From: "Frédéric Danis" <frederic.danis@collabora.com>
To: Luiz Augusto von Dentz <luiz.dentz@gmail.com>
Cc: linux-bluetooth@vger.kernel.org
Subject: Re: [PATCH BlueZ] audio/avrcp: Poll GetPlayStatus when position changed event unsupported
Date: Wed, 23 Sep 2026 16:27:05 +0200 [thread overview]
Message-ID: <53355fb8-db74-427d-9135-cfeccb66e4b0@collabora.com> (raw)
In-Reply-To: <CABBYNZKN2C_65XH_LOptU_GVoarEa_BQmhvzr+7O68Y+Um9Zzg@mail.gmail.com>
Hi Luiz,
On 23/09/2026 15:55, Luiz Augusto von Dentz wrote:
> Hi Frederic,
>
> On Wed, Sep 23, 2026 at 4:02 AM Frédéric Danis
> <frederic.danis@collabora.com> wrote:
>> 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/forward-seek/
>> reverse-seek) and is stopped otherwise, and is torn down on session
>> destruction.
> Back in the days AVRCP was introduced we decided not to poll the
> status and instead have the upper layer trigger it via
> https://github.com/bluez/bluez/blob/master/doc/org.bluez.MediaPlayer.rst#uint32-position-readonly,
> that said I do agree that perhaps it is better to it with directly by
> bluetoothd since so many years have passed and some manufactorers
> insist in not having AVRCP_EVENT_PLAYBACK_POS_CHANGED implemented.
This property may be cached by the DBus framework used, and so iiuc
reading it will not trigger GetPlayStatus.
>> Assisted-by: Copilot CLI:claude-sonnet-5
>> ---
>> profiles/audio/avrcp.c | 64 +++++++++++++++++++++++++++++++++++++++++-
>> 1 file changed, 63 insertions(+), 1 deletion(-)
>>
>> diff --git a/profiles/audio/avrcp.c b/profiles/audio/avrcp.c
>> index df5f97eaf..105538cdc 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,56 @@ 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:
>> + case AVRCP_PLAY_STATUS_FWD_SEEK:
>> + case AVRCP_PLAY_STATUS_REV_SEEK:
>> + avrcp_start_pos_polling(session);
> Btw, if I recall correctly, position tracking really breaks when the
> user seeks the stream using the player progression bar, which
> interrupts the continuous timer. So, if the player does either
> AVRCP_PLAY_STATUS_FWD_SEEK or AVRCP_PLAY_STATUS_REV_SEEK then we can
> resink the position once, afaik iOS media player doesn't use them
> either, thus why the whole polling thing is needed to capture non
> continuguos skips that are not advertised.
>
>> + break;
>> + 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 +2305,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 +4183,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 +4198,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 +4529,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
>>
>>
>
--
Frédéric Danis
Senior Software Engineer
Collabora Ltd.
Platinum Building, St John's Innovation Park, Cambridge CB4 0DS, United Kingdom
Registered in England & Wales, no. 5513718
prev parent reply other threads:[~2026-09-23 14:27 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-23 8:00 [PATCH BlueZ] audio/avrcp: Poll GetPlayStatus when position changed event unsupported Frédéric Danis
2026-09-23 11:04 ` [BlueZ] " bluez.test.bot
2026-09-23 13:55 ` [PATCH BlueZ] " Luiz Augusto von Dentz
2026-09-23 14:27 ` Frédéric Danis [this message]
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=53355fb8-db74-427d-9135-cfeccb66e4b0@collabora.com \
--to=frederic.danis@collabora.com \
--cc=linux-bluetooth@vger.kernel.org \
--cc=luiz.dentz@gmail.com \
/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