mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2026-06-06 03:02:54 -04:00
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:
commit
3b79ecad7a
4 changed files with 498 additions and 14 deletions
|
|
@ -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)))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \}
|
* \}
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,6 +3953,19 @@ 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:
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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) */
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue