Merge branch 'spa_bluez_follow_MediaPlayer1' into 'master'

Draft: RFC: spa: bluez: follow MediaPlayer1 objects and set media.* keys in a2dp-source

See merge request pipewire/pipewire!802
This commit is contained in:
Barnabás Pőcze 2021-08-20 09:02:01 +00:00
commit 3b79ecad7a
4 changed files with 498 additions and 14 deletions

View file

@ -307,6 +307,40 @@ static inline bool spa_atod(const char *str, double *val)
return true; return true;
} }
/**
* Find \a needle in the \a haystack array of \a n strings.
*
* \param haystack an array of \a n strings
* \param n number of strings in \a haystack
* \param cmp a three-way comparator
* \param index pointer to save the index into if \a needle is found,
* may be NULL; it is not modified if \a needle is not found
*
* \return true if \a needle is found, false otherwise
*
* \since 0.3.31
*/
static inline bool spa_find_str(const char * const haystack[], size_t n,
const char *needle,
int (*cmp)(const char *, const char *),
size_t *index)
{
for (size_t i = 0; i < n; i++) {
if (cmp(needle, haystack[i]) != 0)
continue;
if (index != NULL)
*index = i;
return true;
}
return false;
}
#define spa_find_str_arr(haystack, needle, cmp, index) \
(spa_find_str((haystack), SPA_N_ELEMENTS(haystack), (needle), (cmp), (index)))
/** /**
* \} * \}
*/ */

View file

@ -123,6 +123,9 @@ struct impl {
struct spa_bt_transport *transport; struct spa_bt_transport *transport;
struct spa_hook transport_listener; struct spa_hook transport_listener;
const struct spa_bt_player *player;
struct spa_hook device_listener;
struct port port; struct port port;
unsigned int started:1; unsigned int started:1;
@ -762,24 +765,44 @@ static int impl_node_send_command(void *object, const struct spa_command *comman
static void emit_node_info(struct impl *this, bool full) static void emit_node_info(struct impl *this, bool full)
{ {
char latency[64] = SPA_STRINGIFY(MIN_LATENCY)"/48000"; const uint64_t old = full ? this->info.change_mask : 0;
uint64_t old = full ? this->info.change_mask : 0;
struct spa_dict_item node_info_items[] = {
{ SPA_KEY_DEVICE_API, "bluez5" },
{ SPA_KEY_MEDIA_CLASS, this->is_input ? "Audio/Source" : "Stream/Output/Audio" },
{ SPA_KEY_NODE_LATENCY, latency },
{ "media.name", ((this->transport && this->transport->device->name) ?
this->transport->device->name : "A2DP") },
{ SPA_KEY_NODE_DRIVER, this->is_input ? "true" : "false" },
};
if (full) if (full)
this->info.change_mask = this->info_all; this->info.change_mask = this->info_all;
if (this->info.change_mask) { if (this->info.change_mask) {
const char * const device_name = this->transport && this->transport->device->name
? this->transport->device->name
: "A2DP";
const char * const artist = this->player ? this->player->track.artist : NULL;
const char * const title = this->player ? this->player->track.title : NULL;
const char * const album = this->player ? this->player->track.album : NULL;
const char * const software = this->player ? this->player->name : NULL;
char latency[64] = SPA_STRINGIFY(MIN_LATENCY) "/48000";
char media_name[256];
const struct spa_dict_item node_info_items[] = {
{ SPA_KEY_DEVICE_API, "bluez5" },
{ SPA_KEY_MEDIA_CLASS, this->is_input ? "Audio/Source" : "Stream/Output/Audio" },
{ SPA_KEY_NODE_LATENCY, latency },
{ SPA_KEY_NODE_DRIVER, this->is_input ? "true" : "false" },
{ "media.name", media_name },
{ "media.artist", artist },
{ "media.title", title },
{ "media.software", software },
};
snprintf(media_name, sizeof(media_name), "[%s] %s: %s (%s)",
device_name,
artist ? artist : "?",
title ? title : "?",
album ? album : "?"
);
if (this->transport && this->port.have_format) if (this->transport && this->port.have_format)
snprintf(latency, sizeof(latency), "%d/%d", (int)this->props.min_latency, snprintf(latency, sizeof(latency), "%d/%d", (int)this->props.min_latency,
(int)this->port.current_format.info.raw.rate); (int)this->port.current_format.info.raw.rate);
this->info.props = &SPA_DICT_INIT_ARRAY(node_info_items); this->info.props = &SPA_DICT_INIT_ARRAY(node_info_items);
spa_node_emit_info(&this->hooks, &this->info); spa_node_emit_info(&this->hooks, &this->info);
this->info.change_mask = old; this->info.change_mask = old;
@ -1232,6 +1255,16 @@ static const struct spa_node_methods impl_node = {
.process = impl_node_process, .process = impl_node_process,
}; };
static void impl_remove_hooks(struct impl *this)
{
if (this->transport) {
spa_hook_remove(&this->transport_listener);
if (this->transport->device)
spa_hook_remove(&this->device_listener);
}
}
static int do_transport_destroy(struct spa_loop *loop, static int do_transport_destroy(struct spa_loop *loop,
bool async, bool async,
uint32_t seq, uint32_t seq,
@ -1240,8 +1273,13 @@ static int do_transport_destroy(struct spa_loop *loop,
void *user_data) void *user_data)
{ {
struct impl *this = user_data; struct impl *this = user_data;
impl_remove_hooks(this);
this->transport = NULL; this->transport = NULL;
this->transport_acquired = false; this->transport_acquired = false;
this->player = NULL;
return 0; return 0;
} }
@ -1257,6 +1295,76 @@ static const struct spa_bt_transport_events transport_events = {
.destroy = transport_destroy, .destroy = transport_destroy,
}; };
static bool try_set_player(struct impl *this, const struct spa_bt_player *player)
{
if (player->type != SPA_BT_PLAYER_TYPE_AUDIO && player->type != SPA_BT_PLAYER_TYPE_AUDIO_BROADCASTING)
return false;
spa_assert(this->transport->device == player->device);
this->player = player;
return true;
}
static void try_find_player(struct impl *this)
{
const struct spa_bt_player *p;
if (this->transport == NULL)
return;
if (this->transport->device == NULL)
return;
spa_list_for_each(p, &this->transport->device->player_list, device_link)
if (try_set_player(this, p))
break;
}
static void player_added(void *data, struct spa_bt_player *player)
{
struct impl * const this = data;
if (this->player != NULL)
return;
if (try_set_player(this, player))
emit_node_info(this, true);
}
static void player_removed(void *data, struct spa_bt_player *player)
{
struct impl * const this = data;
if (this->player != player)
return;
this->player = NULL;
try_find_player(this);
emit_node_info(this, true);
}
static void player_changed(void *data, struct spa_bt_player *player)
{
struct impl * const this = data;
if (this->player == NULL)
try_set_player(this, player);
if (this->player != player)
return;
emit_node_info(this, true);
}
static const struct spa_bt_device_events device_events = {
SPA_VERSION_BT_DEVICE_EVENTS,
.player_added = player_added,
.player_changed = player_changed,
.player_removed = player_removed,
};
static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface)
{ {
struct impl *this; struct impl *this;
@ -1277,12 +1385,15 @@ static int impl_get_interface(struct spa_handle *handle, const char *type, void
static int impl_clear(struct spa_handle *handle) static int impl_clear(struct spa_handle *handle)
{ {
struct impl *this = (struct impl *) handle; struct impl *this = (struct impl *) handle;
if (this->codec_data) if (this->codec_data)
this->codec->deinit(this->codec_data); this->codec->deinit(this->codec_data);
if (this->codec_props && this->codec->clear_props) if (this->codec_props && this->codec->clear_props)
this->codec->clear_props(this->codec_props); this->codec->clear_props(this->codec_props);
if (this->transport)
spa_hook_remove(&this->transport_listener); impl_remove_hooks(this);
return 0; return 0;
} }
@ -1395,6 +1506,9 @@ impl_init(const struct spa_handle_factory *factory,
spa_bt_transport_add_listener(this->transport, spa_bt_transport_add_listener(this->transport,
&this->transport_listener, &transport_events, this); &this->transport_listener, &transport_events, this);
spa_bt_device_add_listener(this->transport->device, &this->device_listener, &device_events, this);
try_find_player(this);
return 0; return 0;
} }

View file

@ -77,6 +77,7 @@ struct spa_bt_monitor {
struct spa_list device_list; struct spa_list device_list;
struct spa_list remote_endpoint_list; struct spa_list remote_endpoint_list;
struct spa_list transport_list; struct spa_list transport_list;
struct spa_list player_list;
unsigned int filters_added:1; unsigned int filters_added:1;
unsigned int objects_listed:1; unsigned int objects_listed:1;
@ -747,6 +748,238 @@ static void adapter_free(struct spa_bt_adapter *adapter)
free(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 *spa_bt_device_find(struct spa_bt_monitor *monitor, const char *path)
{ {
struct spa_bt_device *d; struct spa_bt_device *d;
@ -783,6 +1016,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->remote_endpoint_list);
spa_list_init(&d->transport_list); spa_list_init(&d->transport_list);
spa_list_init(&d->codec_switch_list); spa_list_init(&d->codec_switch_list);
spa_list_init(&d->player_list);
spa_hook_list_init(&d->listener_list); spa_hook_list_init(&d->listener_list);
@ -806,6 +1040,7 @@ static void device_free(struct spa_bt_device *device)
struct spa_bt_remote_endpoint *ep, *tep; struct spa_bt_remote_endpoint *ep, *tep;
struct spa_bt_a2dp_codec_switch *sw; struct spa_bt_a2dp_codec_switch *sw;
struct spa_bt_transport *t, *tt; struct spa_bt_transport *t, *tt;
struct spa_bt_player *p, *tp;
struct spa_bt_monitor *monitor = device->monitor; struct spa_bt_monitor *monitor = device->monitor;
spa_log_debug(monitor->log, "%p", device); spa_log_debug(monitor->log, "%p", device);
@ -836,6 +1071,11 @@ static void device_free(struct spa_bt_device *device)
spa_list_consume(sw, &device->codec_switch_list, device_link) spa_list_consume(sw, &device->codec_switch_list, device_link)
a2dp_codec_switch_free(sw); 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); spa_list_remove(&device->link);
free(device->path); free(device->path);
free(device->alias); free(device->alias);
@ -3356,6 +3596,21 @@ static void interface_added(struct spa_bt_monitor *monitor,
if (d) if (d)
spa_bt_device_emit_profiles_changed(d, d->profiles, d->connected_profiles); 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) static void interfaces_added(struct spa_bt_monitor *monitor, DBusMessageIter *arg_iter)
@ -3418,6 +3673,12 @@ static void interfaces_removed(struct spa_bt_monitor *monitor, DBusMessageIter *
if (d) if (d)
spa_bt_device_emit_profiles_changed(d, d->profiles, d->connected_profiles); 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); dbus_message_iter_next(&it);
@ -3518,6 +3779,7 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
struct spa_bt_device *d; struct spa_bt_device *d;
struct spa_bt_remote_endpoint *ep; struct spa_bt_remote_endpoint *ep;
struct spa_bt_transport *t; struct spa_bt_transport *t;
struct spa_bt_player *p;
monitor->objects_listed = false; monitor->objects_listed = false;
@ -3534,6 +3796,8 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
device_free(d); device_free(d);
spa_list_consume(a, &monitor->adapter_list, link) spa_list_consume(a, &monitor->adapter_list, link)
adapter_free(a); adapter_free(a);
spa_list_consume(p, &monitor->player_list, link)
player_free(p);
} }
if (has_new_owner) { if (has_new_owner) {
@ -3689,7 +3953,20 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
transport_update_props(transport, &it[1], NULL); 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: fail:
dbus_error_free(&err); dbus_error_free(&err);
@ -3749,6 +4026,10 @@ static void add_filters(struct spa_bt_monitor *this)
"type='signal',sender='" BLUEZ_SERVICE "'," "type='signal',sender='" BLUEZ_SERVICE "',"
"interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'," "interface='org.freedesktop.DBus.Properties',member='PropertiesChanged',"
"arg0='" BLUEZ_MEDIA_TRANSPORT_INTERFACE "'", &err); "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; this->filters_added = true;
@ -3813,6 +4094,7 @@ static int impl_clear(struct spa_handle *handle)
struct spa_bt_device *d; struct spa_bt_device *d;
struct spa_bt_remote_endpoint *ep; struct spa_bt_remote_endpoint *ep;
struct spa_bt_transport *t; struct spa_bt_transport *t;
struct spa_bt_player *p;
monitor = (struct spa_bt_monitor *) handle; monitor = (struct spa_bt_monitor *) handle;
@ -3837,6 +4119,8 @@ static int impl_clear(struct spa_handle *handle)
device_free(d); device_free(d);
spa_list_consume(a, &monitor->adapter_list, link) spa_list_consume(a, &monitor->adapter_list, link)
adapter_free(a); adapter_free(a);
spa_list_consume(p, &monitor->player_list, link)
player_free(p);
if (monitor->backend_native) { if (monitor->backend_native) {
spa_bt_backend_free(monitor->backend_native); spa_bt_backend_free(monitor->backend_native);
@ -4049,6 +4333,7 @@ impl_init(const struct spa_handle_factory *factory,
spa_list_init(&this->device_list); spa_list_init(&this->device_list);
spa_list_init(&this->remote_endpoint_list); spa_list_init(&this->remote_endpoint_list);
spa_list_init(&this->transport_list); spa_list_init(&this->transport_list);
spa_list_init(&this->player_list);
if ((res = parse_codec_array(this, info)) < 0) if ((res = parse_codec_array(this, info)) < 0)
return res; return res;

View file

@ -50,6 +50,7 @@ extern "C" {
#define BLUEZ_MEDIA_INTERFACE BLUEZ_SERVICE ".Media1" #define BLUEZ_MEDIA_INTERFACE BLUEZ_SERVICE ".Media1"
#define BLUEZ_MEDIA_ENDPOINT_INTERFACE BLUEZ_SERVICE ".MediaEndpoint1" #define BLUEZ_MEDIA_ENDPOINT_INTERFACE BLUEZ_SERVICE ".MediaEndpoint1"
#define BLUEZ_MEDIA_TRANSPORT_INTERFACE BLUEZ_SERVICE ".MediaTransport1" #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 BLUEZ_SERVICE ".BatteryProvider1"
#define BLUEZ_INTERFACE_BATTERY_PROVIDER_MANAGER BLUEZ_SERVICE ".BatteryProviderManager1" #define BLUEZ_INTERFACE_BATTERY_PROVIDER_MANAGER BLUEZ_SERVICE ".BatteryProviderManager1"
@ -411,6 +412,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_a2dp_codec_switch;
struct spa_bt_transport; struct spa_bt_transport;
struct spa_bt_player;
struct spa_bt_device_events { struct spa_bt_device_events {
#define SPA_VERSION_BT_DEVICE_EVENTS 0 #define SPA_VERSION_BT_DEVICE_EVENTS 0
@ -427,6 +429,12 @@ struct spa_bt_device_events {
/** Device freed */ /** Device freed */
void (*destroy) (void *data); 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 { struct spa_bt_device {
@ -460,6 +468,7 @@ struct spa_bt_device {
struct spa_list remote_endpoint_list; struct spa_list remote_endpoint_list;
struct spa_list transport_list; struct spa_list transport_list;
struct spa_list codec_switch_list; struct spa_list codec_switch_list;
struct spa_list player_list;
uint8_t battery; uint8_t battery;
int has_battery; int has_battery;
@ -644,6 +653,48 @@ static inline enum spa_bt_transport_state spa_bt_transport_state_from_string(con
return SPA_BT_TRANSPORT_STATE_IDLE; 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_AG_VOLUME 1.0f
#define DEFAULT_RX_VOLUME 1.0f #define DEFAULT_RX_VOLUME 1.0f
#define DEFAULT_TX_VOLUME 0.064f /* spa_bt_volume_hw_to_linear(40, 100) */ #define DEFAULT_TX_VOLUME 0.064f /* spa_bt_volume_hw_to_linear(40, 100) */