Skip to content

feat: audio playback position reports test scene (89,-12) - #99

Open
LautaroPetaccio wants to merge 5 commits into
mainfrom
feat/audio-playback-position-scene
Open

LautaroPetaccio wants to merge 5 commits into
mainfrom
feat/audio-playback-position-scene

Conversation

@LautaroPetaccio

@LautaroPetaccio LautaroPetaccio commented Sep 17, 2026 •

Copy link
Copy Markdown

What you see

Three large cubes behind the buttons, and a clip that beeps on every whole second. Press PLAY and listen:

  • CLOCK flashes on each whole second of the scene's own clock, what a scene assumes the audio is doing.
  • AUDIO flashes on each whole second of clock minus the lag measured from resolved reports. It lines up with the beeps.
  • NAIVE flashes on clock minus a lag measured at the moment the report arrived. It is off by the report's transport delay.

CLOCK running early is the renderer's start delay (100 to 250 ms on the Unity explorer, different every start). NAIVE being off is why the report carries a tick and not a timestamp: a report says where the clip was at tick N but reaches the scene a few ticks later, so it has to be compared against the scene clock at tick N.

That per-tick lookup lives in the SDK, so this scene does not rebuild it. Both readings come from one audioEventsSystem.registerAudioPlaybackEntity callback, which receives each report already resolved as { report, sceneTime, offset }. AUDIO uses the resolved sceneTime; NAIVE takes the same reading and times it on arrival instead, which is the mistake being demonstrated. The scene clock itself comes from getSceneTimeAtTick.

A headline states the measured figures in one sentence, and a compact panel shows them.

Three buttons in front: PLAY, SEEK 10s (the next report shows where the renderer actually landed), STOP. Two small corner buttons cover the rest of the API: LOOP (the offset wraps while the clock keeps counting) and PUSH CB (unregisters and re-registers the raw playback callback). A side panel shows getAudioPlayback polled each frame, getAudioState, both callback counts (state callbacks do not move on position-only reports) and the state log. Renderers without the feature show "no position reports from this renderer" and only CLOCK flashes.

What AUDIO will not do

It lands close to the beep, not exactly on it. currentOffset is where the decoder is reading, and the output path adds tens of milliseconds more that no field carries. That floor is the point of the note in the scene header, and is recorded in ADR-318.

Opting in

The scene creates its AudioSource with reportPlaybackPosition: true. Without that flag the renderer writes no position at all and every cube but CLOCK stays dark, with nothing to say why, so the scene names the flag in its own fallback message as well as the renderer.

Install

The scene pins the branch build of @dcl/sdk from decentraland/js-sdk-toolchain#1624 until the protocol (decentraland/protocol#488) and SDK changes are released. Only the Unity explorer branch (decentraland/unity-explorer#10123) produces position reports today, so on a released client the scene runs and shows the no-reports message. Design record: ADR-318, decentraland/adr#324.

Related pull requests

Adds scenes/89,-12-audio-playback-position, a standalone scene that
exercises the playback-position reports renderers write into the
AudioEvent component while an AudioSource plays: the new optional
PBAudioEvent fields tickNumber, currentOffset and clipLength, and the
audioEventsSystem additions registerAudioPlaybackEntity,
removeAudioPlaybackEntity and getAudioPlayback.

The scene records its own clock per EngineInfo.tickNumber and, on each
report, shows the lag computed against the clock recorded at
report.tickNumber next to the naive value computed against the clock at
processing time, together with the report cadence and transport delay.
getAudioPlayback is polled every frame alongside the push callback, and
registerAudioEventsEntity is registered on the same entity with both
callback counts displayed to show that position reports do not trigger
state-change callbacks. Buttons cover play from 0, seek to 10 s, stop,
loop toggle (offset wrap) and unregistering the playback callback; a
drift line compares the current lag with the first one after play, and
renderers that never send currentOffset get an explicit message instead
of NaN.

The audio clip is a generated 30 s tone with a beep on every whole
second so seeks and the clock-vs-audio alignment are audible. The scene
pins the feat/audio-event-playback-position branch build of @dcl/sdk
until the protocol and SDK changes are released.
@github-actions

Copy link
Copy Markdown

How to test scene changes in the SEPOLIA World

Every commit pushed to any branch automatically triggers the deployment of the modified scenes to the sdk7testscenes.dcl.eth world on SEPOLIA.

If you need to re-trigger that deployment manually:

  1. Go to the Deploy changed scenes to world action
  2. Click Run workflow, select your branch, and enter the scene folder (e.g. scenes/88,-10-audio-visualization)

Once deployed, test the scene in the Explorer:

  1. Switch MetaMask to the SEPOLIA network (not mainnet)
  2. Open the Explorer using one of these methods:

Three large beat cubes now sit centre stage: CLOCK flashes on the scene
clock, AUDIO on clock minus the lag measured through tickNumber, NAIVE on
clock minus the lag measured at processing time. With the beeping clip the
AUDIO cube lines up with what is heard, CLOCK runs early by the start
delay, and NAIVE is off by the report's transport delay, which is the
whole reason the field is a tick and not a timestamp.

Three buttons in front (PLAY, SEEK, STOP), one headline that states the
result in a sentence, one compact panel with the measured numbers. The
rest of the API coverage (getAudioPlayback polling, getAudioState, both
callback counts, the state log, loop wrap, callback unregistering) moves
to a side panel and two small corner buttons. No coverage removed.
@LautaroPetaccio
LautaroPetaccio marked this pull request as ready for review September 17, 2026 14:28
…ebuilding it

The scene kept its own map of scene clock per EngineInfo.tickNumber, which is
exactly the piece audioEventsSystem now provides. A showcase that hand-rolls it
teaches the opposite of the feature.

The correct reading now comes from registerAudioPlaybackSampleEntity, which
hands over each report already resolved against the scene clock at the tick it
was sampled in, and the scene clock itself comes from getSceneTimeAtTick. The
naive reading still uses the raw callback timed on arrival, so the contrast the
three cubes demonstrate is unchanged.

Also note the accuracy floor in the header: the reported playhead is the
decoder's, and output latency sits on top of it uncarried, so the AUDIO cube
lands close to the beep rather than exactly on it.

Repin @dcl/sdk to the current branch build, which carries the sample API.
The SDK collapsed its two playback registrations into one that hands over the
resolved reading, so the scene no longer needs a callback each for the correct
and naive figures. Both now come from the same reading in one place, which is a
tighter demonstration: same report, two ways of timing it, and the gap between
the cubes is exactly the transport delay.

The two callback counters collapse into one for the same reason.

Repin @dcl/sdk to the build carrying the merged API.

@pravusjif pravusjif left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As long as the test scene is already useful to you (and QA) to test the feature you can go ahead and merge!

You can test it live following those instructions: #99 (comment)

Position reports are now opt-in per source: PBAudioSource gained
reportPlaybackPosition (default false). Media-state changes are reported
either way, so a scene that registers a playback callback without the
flag receives nothing and gets no error about it.

Set it on the scene's AudioSource, document it in the header block next
to the PBAudioEvent fields and at the top of the API-under-test list, and
name it in the on-screen fallback line, which now has two causes rather
than one.

Repin @dcl/sdk and @dcl/js-runtime to the branch build carrying the
field (35640312842, commit 7ee164e).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants