From: "Frédéric Danis" <frederic.danis@collabora.com>
To: linux-bluetooth@vger.kernel.org
Subject: [PATCH BlueZ v2] audio/avrcp: Poll GetPlayStatus when position changed event unsupported
Date: Fri, 25 Sep 2026 16:10:47 +0200 [thread overview]
Message-ID: <20260925141047.3397212-1-frederic.danis@collabora.com> (raw)
Some remote targets do not support the AVRCP_EVENT_PLAYBACK_POS_CHANGED
notification, meaning bluetoothd never learns about track position
updates for such devices.
When this event is not present in the capabilities reported by the
remote, fall back to polling AVRCP_GET_PLAY_STATUS once a second so
the media player position stays in sync. Polling is only kept running
while playback is actively progressing (playing) and is stopped
otherwise, and is torn down on session destruction.
Assisted-by: Copilot CLI:claude-sonnet-5
---
v1->v2: Remove polling during seek
profiles/audio/avrcp.c | 67 +++++++++++++++++++++++++++++++++++++++++-
1 file changed, 66 insertions(+), 1 deletion(-)
diff --git a/profiles/audio/avrcp.c b/profiles/audio/avrcp.c
index df5f97eaf..a30709eaa 100644
--- a/profiles/audio/avrcp.c
+++ b/profiles/audio/avrcp.c
@@ -247,7 +247,9 @@ struct avrcp {
unsigned int control_id;
unsigned int browsing_id;
unsigned int browsing_timer;
+ unsigned int position_timer;
uint16_t supported_events;
+ bool pos_changed_supported;
uint8_t transaction;
struct pending_pdu *pending_pdu;
};
@@ -341,6 +343,7 @@ static const uint32_t company_ids[] = {
static void avrcp_register_notification(struct avrcp *session, uint8_t event);
static GList *player_list_settings(struct avrcp_player *player);
+static void avrcp_get_play_status(struct avrcp *session);
static void avrcp_browsing_record(sdp_record_t *record, sdp_data_t *version)
{
@@ -2224,6 +2227,59 @@ static const char *status_to_string(uint8_t status)
}
}
+static bool avrcp_poll_play_status(gpointer user_data)
+{
+ struct avrcp *session = user_data;
+
+ avrcp_get_play_status(session);
+
+ return TRUE;
+}
+
+static void avrcp_stop_pos_polling(struct avrcp *session)
+{
+ if (session->position_timer == 0)
+ return;
+
+ timeout_remove(session->position_timer);
+ session->position_timer = 0;
+}
+
+static void avrcp_start_pos_polling(struct avrcp *session)
+{
+ if (session->position_timer > 0)
+ return;
+
+ session->position_timer = timeout_add_seconds(1,
+ avrcp_poll_play_status,
+ session, NULL);
+}
+
+/*
+ * Poll GetPlayStatus once a second to resync position while playing when
+ * the remote does not support AVRCP_EVENT_PLAYBACK_POS_CHANGED. Polling is
+ * paused whenever playback is not actively progressing.
+ */
+static void avrcp_update_pos_polling(struct avrcp *session, uint8_t status)
+{
+ if (session->pos_changed_supported)
+ return;
+
+ switch (status) {
+ case AVRCP_PLAY_STATUS_PLAYING:
+ avrcp_start_pos_polling(session);
+ break;
+ case AVRCP_PLAY_STATUS_FWD_SEEK:
+ case AVRCP_PLAY_STATUS_REV_SEEK:
+ /* Position already resynced once via GetPlayStatus,
+ * no need for continuous polling on seek.
+ */
+ default:
+ avrcp_stop_pos_polling(session);
+ break;
+ }
+}
+
static gboolean avrcp_get_play_status_rsp(struct avctp *conn, uint8_t code,
uint8_t subunit, uint8_t transaction,
uint8_t *operands, size_t operand_count,
@@ -2252,6 +2308,8 @@ static gboolean avrcp_get_play_status_rsp(struct avctp *conn, uint8_t code,
media_player_set_position(mp, position);
media_player_set_status(mp, status_to_string(status));
+ avrcp_update_pos_polling(session, status);
+
return FALSE;
}
@@ -4128,6 +4186,9 @@ static gboolean avrcp_get_capabilities_resp(struct avctp *conn, uint8_t code,
}
}
+ session->pos_changed_supported =
+ !!(events & (1 << AVRCP_EVENT_PLAYBACK_POS_CHANGED));
+
if (!session->controller || !session->controller->player)
return FALSE;
@@ -4140,7 +4201,8 @@ static gboolean avrcp_get_capabilities_resp(struct avctp *conn, uint8_t code,
!(events & (1 << AVRCP_EVENT_SETTINGS_CHANGED)))
avrcp_list_player_attributes(session);
- if (!(events & (1 << AVRCP_EVENT_STATUS_CHANGED)))
+ if (!(events & (1 << AVRCP_EVENT_STATUS_CHANGED)) ||
+ !session->pos_changed_supported)
avrcp_get_play_status(session);
if (!(events & (1 << AVRCP_EVENT_STATUS_CHANGED)))
@@ -4470,6 +4532,9 @@ static void session_destroy(struct avrcp *session, int err)
if (session->browsing_timer > 0)
timeout_remove(session->browsing_timer);
+ if (session->position_timer > 0)
+ timeout_remove(session->position_timer);
+
if (session->controller != NULL)
controller_destroy(session);
--
2.43.0
next reply other threads:[~2026-09-25 14:10 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-25 14:10 Frédéric Danis [this message]
2026-09-25 15:58 ` [BlueZ,v2] audio/avrcp: Poll GetPlayStatus when position changed event unsupported bluez.test.bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260925141047.3397212-1-frederic.danis@collabora.com \
--to=frederic.danis@collabora.com \
--cc=linux-bluetooth@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox