Linux bluetooth development
 help / color / mirror / Atom feed
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



      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