wavesurfer.js icon indicating copy to clipboard operation
wavesurfer.js copied to clipboard

Weird line shows up and playback glitches on iOS + playback delay on android

Open qbikmusik opened this issue 10 months ago • 2 comments

Bug description

So on both android and ios, when you click play on the waveforms, on android there is a weird delay that looks like latency when its clicked and on ios and it does almost like a rubberbanding. On longer waveforms the rubberbanding is continuous. On Desktop neither of these issues are present.

The more pressing issue is that on iOS only, there is a weird line that goes down the middle of the waveforms that isnt present on any other devices.

Environment

  • Browser: Chrome & Safari

Minimal code snippet

Here is the script powering all the waveforms:

document.addEventListener("DOMContentLoaded", () => { // Select waveform wrappers separately for each section var sectionOneWrappers = document.querySelectorAll('.ggq83qBf_B .waveform-wrapper, .gs6Xi0fYl5 .waveform-wrapper, .gLTmrfM0-x .waveform-wrapper, .gUnZ4HGJKa .waveform-wrapper, .gnRRDzJs7T .waveform-wrapper, .gCJ0ZwOMxK .waveform-wrapper, .gKiBm8XDAg .waveform-wrapper, .gEY7Fb4b80 .waveform-wrapper, .gS0eoEVJa_ .waveform-wrapper, .gbRvKRBzrw .waveform-wrapper, .gyL0mSwsZ9 .waveform-wrapper');

var sectionTwoWrappers = document.querySelectorAll('.gh7SaPNZE_ .waveform-wrapper, .gbwLR70M44 .waveform-wrapper, .g43ePaRQNY .waveform-wrapper, .gzUur5mnje .waveform-wrapper, .gxphDIbMm1 .waveform-wrapper, .gxjX0F221J .waveform-wrapper, .gFy4UTfmVa .waveform-wrapper, .gbFMbXB2oH .waveform-wrapper, .g161qy93on .waveform-wrapper, .gYnBfOIENQ .waveform-wrapper');

var playIcon = `<svg class="gf_playicon" height="20" width="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path fill="currentColor" d="M240,128a15.74,15.74,0,0,1-7.6,13.51L88.32,229.65a16,16,0,0,1-16.2.3A15.86,15.86,0,0,1,64,216.13V39.87a15.86,15.86,0,0,1,8.12-13.82,16.2.3L232.4,114.49A15.74,15.74,0,0,1,240,128Z"></path></svg>`;

var pauseIcon = `<svg class="gf_pauseicon" height="20" width="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path fill="currentColor" d="M216,48V208a16,16,0,0,1-16,16H160a16,16,0,0,1-16-16V48a16,16,0,0,1,16-16h40A16,16,0,0,1,216,48ZM96,32H56A16,16,0,0,0,40,48V208a16,16,0,0,0,16,16H96a16,16,0,0,0,16-16V48A16,16,0,0,0,96,32Z"></path></svg>`;

let allWaveforms = []; // Store all active waveforms across sections

// Function to initialize waveforms per section
function addWaveform(wsWrapper, progressColor) {
    wsWrapper.forEach((el, i) => {
        var url = el.getAttribute('data-audio-url');
        var color = el.getAttribute('data-audio-color');
        var wf = el.querySelector('.waveform');
        var button = el.querySelector('.play-button');

        if (!url || !wf || !button) {
            console.error(`Missing data for waveform at index ${i}`);
            return;
        }

        button.innerHTML = playIcon;

        // Initialize WaveSurfer with unique settings per section
        const wavesurfer = WaveSurfer.create({
            container: wf,
            waveColor: color,
            progressColor: progressColor,
            height: 35,
            cursorWidth: 0,
            normalize: true,
            partialRender: true,

});

        wavesurfer.load(url);
        allWaveforms.push({ wavesurfer, button });

        wavesurfer.on('play', () => {
            console.log(`Waveform ${i} playing in section with color ${progressColor}`);
            button.innerHTML = pauseIcon;

            // Pause all other playing waveforms (even across sections)
            allWaveforms.forEach(({ wavesurfer: wfInstance, button: btn }) => {
                if (wfInstance !== wavesurfer && wfInstance.isPlaying()) {
                    wfInstance.pause();
                    btn.innerHTML = playIcon;
                }
            });
        });

        wavesurfer.on('pause', () => {
            console.log(`Waveform ${i} paused`);
            button.innerHTML = playIcon;
        });

        button.addEventListener('click', function () {
            wavesurfer.playPause();
        });
    });
}

// Initialize waveforms for each section with different settings
addWaveform(sectionOneWrappers, "#59E552"); // Green progress for first section
addWaveform(sectionTwoWrappers, "#BF0CE9"); // Red progress for second section


// Ensure AudioContext resumes on first user interaction
document.addEventListener('click', () => {
    allWaveforms.forEach(({ wavesurfer }) => {
        if (wavesurfer.backend.ac.state === 'suspended') {
            wavesurfer.backend.ac.resume();
        }
    });
}, { once: true });

});

And here is the css:

.waveform { width: 100%; }

.waveform div:empty { display: block; }

.waveform-wrapper { display: flex; flex-direction: column; gap: 10px; align-items: center; }

.waveform-wrapper .play-button { cursor: pointer; } .waveform-wrapper .track-name { position: relative; color: white; text-align: center; font-family: 'Oswald'; } .waveform-wrapper .waveform-div { gap: 10px; display: flex; align-items: center; width: 100%; } .waveform-wrapper .play-button svg { max-width: unset !important; color: #E5E9EC; border: 1px solid #E5E9EC; border-radius: 50%; padding: 5px; width: 30px; height: 30px; } .waveform wave wave canvas{ max-width: unset !important; }

Expected result

I expect it to look and perform the same as on desktop like it used too in v6, v7 introduced these issues

Obtained result

Screenshots

Here is a video demonstrating what im talking about: https://streamable.com/rwrhom

qbikmusik avatar Jun 27 '25 07:06 qbikmusik

We’re also running into the same stutter/rubber-banding on iOS where the playback cursor jumps back at the start of playing even though the audio continues uninterrupted. We’ve reproduced it both in our app and on wavesurfer.js docs/examples (iPhone 15, iOS 17.x, Safari)(OSX Sequoia, Safari 18.4).

Given how many iOS/Safari-audio tickets were closed a few years back, could this be something in WebAudio/Safari itself rather than wavesurfer.js? Any ideas on fixes if there are any (backend, normalize, etc.)?

Update: the rubberbanding might be an issue with Safari itself as I am seeing the same bug on other waveform/audio lib examples e.g. https://waveform.prototyping.bbc.co.uk/

Recreation on wavesurfer.js:

https://github.com/user-attachments/assets/69a3c46a-c69e-4dc6-881c-07acba880a5e

Jacksonmills avatar Jul 01 '25 22:07 Jacksonmills

@Jacksonmills the playback is through a regular audio element, so it's most likely a browser issue indeed. You can insert a simple <audio src="..." /> element and see if the problem persists.

backend: 'WebAudio' might help but it comes with its own quirks.

katspaugh avatar Jul 02 '25 07:07 katspaugh