Skip to content

Playlist: display fallback seekbar style waveform for random waveforms - #81028

Open
Intenzi wants to merge 4 commits into
WordPress:trunkfrom
Intenzi:fix/playlist-seekbar-fallback
Open

Intenzi wants to merge 4 commits into
WordPress:trunkfrom
Intenzi:fix/playlist-seekbar-fallback

Conversation

@Intenzi

@Intenzi Intenzi commented Jul 31, 2026

Copy link
Copy Markdown

What?

Closes #80534 (Follow-up to Trac #65673 and #80533 )

Associated Upstream Issue: arraypress/waveform-player#19
Associated Upstream PR: arraypress/waveform-player#20

  • Bumps @arraypress/waveform-player to v1.24.0: Pulls in the upstream release that adds the waveform-is-placeholder class to the player container when audio analysis fails (e.g., due to CORS or network errors).
  • Fallback styling: When waveform is a placeholder, changes waveform style to a flat built-in seekbar visualization.
  • Restores to original styling: When viewing a healthy track, reverts back to the preferred selected waveform style.

Why?

Referring from issue #80534 :

#80533 fixes Playlist block playback for tracks served from hosts without CORS headers (e.g. media offloaded to a CDN) by clearing the crossOrigin attribute the @arraypress/waveform-player library hardcodes on its audio element.

The waveform visualization for such tracks is however still affected. Since the audio file needs to be fetched via fetch() for generating a waveform, if the audio file's src is a third party such as Amazon S3.. that fetch is blocked by CORS policy. The waveform library would then silently fallback to randomly generated placeholder peaks for the audio track. The user would see a waveform that looks very realistic but has no relationship to the actual audio. Which is considered bad/misleading UX.

How?

  • Raised Issue/PR upstream to add waveform-is-placeholder class to the player's container element when the track could not be fetched for waveform analysis.

Associated Upstream Issue: arraypress/waveform-player#19
Associated Upstream PR: arraypress/waveform-player#20

Inside gutenberg/packages/block-library/utils/waveform-utils.js:

  • Utilised the updated upstream player library's onLoad callback to check if the waveform-is-placeholder class is present on the player's container.
  • Added a fallback to completely flat seekbar style for when the waveform generated is a random placeholder.
  • Implemented equivalent revert to original preferred waveform style for when the generated waveform is valid.

Testing Instructions

Here are the formatted Testing Instructions you can copy directly into your PR description:

Testing Instructions

  1. Ensure your local environment is running and build assets are up to date:
    npm i
    npm run wp-env start
    npm start
  2. Log into the WordPress dashboard as an administrator.
  3. Go to Media > Add New Media File and upload any sample .mp3 audio file.
  4. Create a new post or page.
  5. Insert a Playlist block and select your uploaded .mp3 file to add it as a track.
  6. To simulate a CORS-blocked external track, switch the editor to Code Editor mode (via the three-dots menu in the top-right corner) and locate the playlist markup:
    • Edit the first track's properties: change its "src" to a third-party audio URL lacking CORS headers (e.g. https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3) and change its "id" value.
  7. Switch back to the Visual Editor. Add the local .mp3 file again as a second track in the playlist (so the playlist now contains: Track 1 = External CORS-blocked, Track 2 = Local).
  8. Save/Publish the post and click Preview to view it.
  9. Verify the visual fallback behavior:
    • Click Track 1 (External): Verify that the player automatically falls back to a clean, completely flat seekbar style because audio analysis was blocked by CORS.
    • Click Track 2 (Local): Verify that the player successfully restores and renders the default bars style.

Testing Instructions for Keyboard

N/A

Screenshots or screencast

Demonstrates automatic waveform style change for same playlist block with different tracks:

8mb.video-Fix.Playlist.Waveform.Gutenberg.mp4

Use of AI Tools

AI assistance: Yes
Tool(s): Antigravity IDE
Model(s): Gemini 3.1 Pro (High)
Used for: Help in identifying the possible implementations and deciding on an optimal resolution. Final implementation was reviewed and tested by me.

@github-actions github-actions Bot added [Package] Block library /packages/block-library First-time Contributor Pull request opened by a first-time contributor to Gutenberg repository labels Jul 31, 2026
@github-actions

Copy link
Copy Markdown

👋 Thanks for your first Pull Request and for helping build the future of Gutenberg and WordPress, @Intenzi! In case you missed it, we'd love to have you join us in our Slack community.

If you want to learn more about WordPress development in general, check out the Core Handbook full of helpful information.

@adamsilverstein adamsilverstein 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.

Nice one!

@adamsilverstein adamsilverstein added the [Type] Bug An existing feature does not function as intended label Aug 13, 2026
@adamsilverstein

adamsilverstein commented Aug 13, 2026 •

Copy link
Copy Markdown
Member

Thanks for this fix @Intenzi - looks good to me. Can you resolve the merge conflicts and we can get this fix in?

@ramonjd

ramonjd commented Sep 15, 2026

Copy link
Copy Markdown
Member

@jeryj 👋🏻 just doing some light triage and wondering if this fix is still required. Cheers!

@jeryj

jeryj commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

I believe @scruffian is familiar with this bug. Could you take a look?

@adamsilverstein

Copy link
Copy Markdown
Member

Gave this a test locally and it does what it says on the tin. One thing worth sorting out is the version bump, which trunk has already moved past.

I had Claude set up the repro and run it both ways, here is what came back:

Tested on a local wp-env build with a two track Playlist: track 1 pointed at https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3 (which serves no Access-Control-Allow-Origin header), track 2 a local upload. Same post, same page, built twice - once with trunk's waveform-utils.js and once with this branch.

Before, the CORS blocked track draws a convincing waveform that has nothing to do with the audio:

After, it falls back to the flat seekbar:

The local track still renders real bars, and switching between the two flips the style correctly in both directions:

Eyeballing two waveforms is not much of a test, so the canvas pixels were measured as well - standard deviation of the per column bar heights. Before: 64.22 on the CORS blocked track vs 63.68 on the local one, ie. statistically indistinguishable, which is the misleading part. After: 0.00 on the CORS blocked track, 63.68 on the local one.

Note on the dependency bump: trunk is already on @arraypress/waveform-player@^1.26.0, past the ^1.24.0 here, and that is what the merge conflict is. Dropping c779b68 and keeping just the waveform-utils.js change should clear it. 1.26.0 still adds waveform-is-placeholder on fetch failure and still calls options.onLoad, so nothing here depends on 1.24 specifically.

Also worth noting there is no visible flash of the placeholder waveform before the seekbar replaces it - the library runs drawWaveform(), renderMarkers() and onLoad() in the same tick, so the redraw lands before the browser paints.

Can you merge trunk and drop the bump commit? I'm happy to re-test once that is in.

@scruffian

Copy link
Copy Markdown
Contributor

I'm not very keen on this idea. I think if users choose bars then we should honour that choice, even if they aren't accurate. I think this will feel like a more broken experience. Instead, why don't we try saving the waveform data in the block?

@adamsilverstein

Copy link
Copy Markdown
Member

Instead, why don't we try saving the waveform data in the block?

Thats a great idea, maybe we can try this.

I'm not very keen on this idea. I think if users choose bars then we should honour that choice, even if they aren't accurate. I think this will feel like a more broken experience.

Has the user chosen bars in this case? I understood it was caused by a CORS error when the Audio URL was on a remote server that didn't send CORS headers. Maybe @Intenzi can clarify.

@Intenzi

Intenzi commented Sep 22, 2026

Copy link
Copy Markdown
Author

Thankyou for the response, I haven't been receiving any mails on this. I'll follow up with a response and required clarifications for the same by today.

@Intenzi

Intenzi commented Sep 22, 2026 •

Copy link
Copy Markdown
Author

To answer @adamsilverstein 's question: Yes, when a user has chosen any playstyle (mirror/dots/etc.) for the track, it will revert to a flat seekbar to visibly display that the playlist block was unable to get the waveform for this track.

I completely agree with @scruffian 's thought process. "If a user wants to display a dot pattern on their song, it should do that and not suddenly show a flat seekbar." is a very valid concern.

There are two technical limitations to why I have not approached storing the waveform into the block locally or in a database:-
a> It doesn't resolve the existing issue: #80534 i.e. any audio file that is stored in externally hosted platform (such as Amazon S3, Cloudflare or CDNs) will face the same issue. We won't be able to obtain the waveform to then be looking towards storing it into the post/block.
As:

  • Playing an audio file requires no permissions (can be done through tag without CORS restrictions)
  • Analyzing the waveform of an audio requires a download of the raw audio file:
export async function generateWaveform(url, samples = DEFAULT_SAMPLES, shouldDetectBPM = false) {
  let audioContext;
  try {
      const AudioCtx = window.AudioContext || window.webkitAudioContext;
      audioContext = new AudioCtx();
      const response = await fetch(url);
      const arrayBuffer = await response.arrayBuffer();
      const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);

      let peaks = extractPeaks(audioBuffer, samples);
      ...
  • This gets blocked and thus we won't obtain the waveform to then be able to followup with an approach of saving it.

b> If we decide to save the waveform data somehow (which is by default an array of 1800 float values per track) into the post content, it will lead to a much larger initial payload size when anyone requests for a page with multiple tracks in the playlist block.


  1. The crux UX reason for the present solution is to prevent any fake waveform from showing up as it is intentionally misleading to a user.

  2. Imagine a track that is completely silent in the beginning but still shows visual peaks. It becomes significantly more manual to identify that there is an issue with obtaining the waveform. This leads to an audio-visual discrepancy for the viewers.

  3. The current solution thus makes it visible in the editor that the track is unable to render the chosen waveform style because the waveform peaks cannot be extracted from the remote audio source they have provided. As a possible enhancement: we could also display the same via text to show an editor-only warning notice to make this clear to the author, while the frontend simply renders the clean seekbar without showing any error notices to visitors.

I looked a bit further, as an industrial precedent : SoundCloud also follows the UX of falling back to a flat seekbar when no metadata could be obtained/waveform couldn't be analysed.

I am open to any clarifications and suggestions, improvements we can utilise to make the experience better for the end user. Please comment if any further questions arise for the PR and the topic.

@scruffian

Copy link
Copy Markdown
Contributor

@Intenzi you make some great points. Can we explore both options - save the waveform locally if possible (as demonstrated in #83086) and then still fallback to showing the seekbar if not possible?

@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown

🤖 PR meta 🤖

🎉 Props

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: Intenzi <intenzi@git.wordpress.org>
Co-authored-by: adamsilverstein <adamsilverstein@git.wordpress.org>
Co-authored-by: ramonjd <ramonopoly@git.wordpress.org>
Co-authored-by: jeryj <jeryj@git.wordpress.org>
Co-authored-by: scruffian <scruffian@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

Updated as activity occurs, without notifying anyone named here. Add the props-bot label to refresh.

@Intenzi

Intenzi commented Oct 19, 2026 •

Copy link
Copy Markdown
Author

👋 @scruffian I've gone through the PR #83086 , I think its a good optimisation towards caching waveforms. Great work there!

I understand that it avoids having to download and decode the full audio file on the front end everytime just to generate the waveform.
Like I mentioned in my earlier comment, I am not very keen on having waveforms being stored directly in the page itself, it feels a little sub-optimised as its only scoped to the page in particular whereas this should be an audio file level optimisation.

I just went through #84712 and I think it makes sense if we can move the saving of the waveform to the database separately as attachment metadata, as proposed there. Would you be willing to look into that approach? I think it's a great middle ground that avoids post bloat and keeps the data reusable across other blocks.

Regardless, the solution in this PR is separate and complimentary :) It targets scenarios where an audio file is missing, deleted or blocked by CORS. Ensuring it cleanly falls back to a flat seekbar rather than a fake waveform. This fallback can be used as the safety net to build further on top the optimisation PR you are working towards 😄 .

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

First-time Contributor Pull request opened by a first-time contributor to Gutenberg repository [Package] Block library /packages/block-library [Type] Bug An existing feature does not function as intended

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Playlist: waveform shows random placeholder peaks for tracks served without CORS headers

5 participants