I’m working on a chat interface that supports uploading and rendering audio messages using WaveSurfer.js. To handle multiple audio attachments, I created a reusable function renderWaveform(uniqueId, url) that initializes the waveform player for each uploaded audio file.
Each uploaded audio message has its own container with:
A play/pause button
A WaveSurfer waveform
A timestamp
When I call renderWaveform(uniqueId, url) after inserting the audio HTML dynamically:
The waveform renders correctly.
The audio plays, but the time display (audioprocess) sometimes shows incorrect or delayed updates.
The finish event occasionally doesn’t reset the play button or display the correct final duration.
Also, when multiple audio files are uploaded, sometimes the waveform doesn’t render or conflicts appear.
Delayed waveform rendering using setTimeout().
Ensured each container has a unique ID (waveform${uniqueId}).
Used wavesurfer.on(‘ready’), on(‘audioprocess’), and on(‘finish’) properly.
Verified the DOM is ready before initializing WaveSurfer.
Ensuring each audio waveform renders and updates its time correctly.
Fixing audioprocess to reliably show the current playback time.
Making sure finish resets the UI state (button + time) for each audio message.
function renderWaveform(uniqueId, url) {
const wavesurfer = WaveSurfer.create({
container: `#waveform${uniqueId}`,
waveColor: "#ccc",
progressColor: "#4CAF50",
barWidth: 2,
height: 30,
responsive: true,
});
wavesurfer.load(url);
wavesurfer.on("ready", () => {
document.getElementById(`text${uniqueId}`).textContent = formatTime(wavesurfer.getDuration());
});
wavesurfer.on("audioprocess", (currentTime) => {
document.getElementById(`text${uniqueId}`).textContent = formatTime(currentTime);
});
wavesurfer.on("finish", () => {
document.getElementById(`text${uniqueId}`).textContent = formatTime(wavesurfer.getDuration());
document.getElementById(`playPause${uniqueId}`).classList.remove("playing");
});
document.getElementById(`playPause${uniqueId}`).addEventListener("click", () => {
wavesurfer.playPause();
});
}
// send Audio File
function appendAudioFile (uniqueId,audioUrl) {
let time = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
const now = new Date();
// let uniqueId = 'id-'
// + now.getFullYear()
// + (now.getMonth() + 1).toString().padStart(2, '0')
// + now.getDate().toString().padStart(2, '0')
// + now.getHours().toString().padStart(2, '0')
// + now.getMinutes().toString().padStart(2, '0')
// + now.getSeconds().toString().padStart(2, '0')
// + index
// + now.getMilliseconds().toString().padStart(3, '0')
// + '-' + Math.floor(Math.random() * 10000);
const chatBox = document.getElementById("chatBox");
chatBox.insertAdjacentHTML("beforeend", `<div class="chatBoxListMessage sent audioAttachment" data-audio-url="${audioUrl}">
<div class="chatbox-message-item sent">
<span class="profileimage">
<svg viewBox="0 0 212 212"
preserveAspectRatio="xMidYMid meet" class="ln8gz9je ppled2lx"
version="1.1" x="0px" y="0px" enable-background="new 0 0 212 212"
xml:space="preserve">
<path fill="currentColor" class="background"
d="M106.251,0.5C164.653,0.5,212,47.846,212,106.25S164.653,212,106.25,212C47.846,212,0.5,164.654,0.5,106.25 S47.846,0.5,106.251,0.5z">
</path>
<g>
<path fill="#FFFFFF" class="primary"
d="M173.561,171.615c-0.601-0.915-1.287-1.907-2.065-2.955c-0.777-1.049-1.645-2.155-2.608-3.299 c-0.964-1.144-2.024-2.326-3.184-3.527c-1.741-1.802-3.71-3.646-5.924-5.47c-2.952-2.431-6.339-4.824-10.204-7.026 c-1.877-1.07-3.873-2.092-5.98-3.055c-0.062-0.028-0.118-0.059-0.18-0.087c-9.792-4.44-22.106-7.529-37.416-7.529 s-27.624,3.089-37.416,7.529c-0.338,0.153-0.653,0.318-0.985,0.474c-1.431,0.674-2.806,1.376-4.128,2.101 c-0.716,0.393-1.417,0.792-2.101,1.197c-3.421,2.027-6.475,4.191-9.15,6.395c-2.213,1.823-4.182,3.668-5.924,5.47 c-1.161,1.201-2.22,2.384-3.184,3.527c-0.964,1.144-1.832,2.25-2.609,3.299c-0.778,1.049-1.464,2.04-2.065,2.955 c-0.557,0.848-1.033,1.622-1.447,2.324c-0.033,0.056-0.073,0.119-0.104,0.174c-0.435,0.744-0.79,1.392-1.07,1.926 c-0.559,1.068-0.818,1.678-0.818,1.678v0.398c18.285,17.927,43.322,28.985,70.945,28.985c27.678,0,52.761-11.103,71.055-29.095 v-0.289c0,0-0.619-1.45-1.992-3.778C174.594,173.238,174.117,172.463,173.561,171.615z">
</path>
<path fill="#FFFFFF" class="primary"
d="M106.002,125.5c2.645,0,5.212-0.253,7.68-0.737c1.234-0.242,2.443-0.542,3.624-0.896 c1.772-0.532,3.482-1.188,5.12-1.958c2.184-1.027,4.242-2.258,6.15-3.67c2.863-2.119,5.39-4.646,7.509-7.509 c0.706-0.954,1.367-1.945,1.98-2.971c0.919-1.539,1.729-3.155,2.422-4.84c0.462-1.123,0.872-2.277,1.226-3.458 c0.177-0.591,0.341-1.188,0.49-1.792c0.299-1.208,0.542-2.443,0.725-3.701c0.275-1.887,0.417-3.827,0.417-5.811 c0-1.984-0.142-3.925-0.417-5.811c-0.184-1.258-0.426-2.493-0.725-3.701c-0.15-0.604-0.313-1.202-0.49-1.793 c-0.354-1.181-0.764-2.335-1.226-3.458c-0.693-1.685-1.504-3.301-2.422-4.84c-0.613-1.026-1.274-2.017-1.98-2.971 c-2.119-2.863-4.646-5.39-7.509-7.509c-1.909-1.412-3.966-2.643-6.15-3.67c-1.638-0.77-3.348-1.426-5.12-1.958 c-1.181-0.355-2.39-0.655-3.624-0.896c-2.468-0.484-5.035-0.737-7.68-0.737c-21.162,0-37.345,16.183-37.345,37.345 C68.657,109.317,84.84,125.5,106.002,125.5z">
</path>
</g>
</svg>
</span>
<span class="p0s8B">
<svg viewBox="0 0 8 13" height="13" width="8"
preserveAspectRatio="xMidYMid meet" class="" version="1.1" x="0px"
y="0px" enable-background="new 0 0 8 13" xml:space="preserve">
<path opacity="0.13"
d="M5.188,1H0v11.193l6.467-8.625 C7.526,2.156,6.958,1,5.188,1z">
</path>
<path fill="currentColor"
d="M5.188,0H0v11.193l6.467-8.625C7.526,1.156,6.958,0,5.188,0z">
</path>
</svg>
</span>
<div class="sentMessagesBox">
<button type="button" class="optionMessage">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M201.4 374.6c12.5 12.5 32.8 12.5 45.3 0l160-160c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L224 306.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l160 160z"/></svg>
</button>
<span class="chatbox-message-item-text">
<div class="audio_file_section">
<div class="loaderSpinner">
<svg class="spinner-container" width="65px" height="65px" viewBox="0 0 52 52">
<circle class="path" cx="26px" cy="26px" r="20px" fill="none" stroke-width="4px"></circle>
</svg>
</div>
<div class="audio_button_playPause">
<button class="playPauseBtn" id="playPause${uniqueId}">
<svg class="playA" viewBox="0 0 34 34" height="34" width="34" preserveAspectRatio="xMidYMid meet" class="" version="1.1" x="0px" y="0px" enable-background="new 0 0 34 34"><path fill="currentColor" d="M8.5,8.7c0-1.7,1.2-2.4,2.6-1.5l14.4,8.3c1.4,0.8,1.4,2.2,0,3l-14.4,8.3 c-1.4,0.8-2.6,0.2-2.6-1.5V8.7z"></path></svg>
<svg class="pauseA" viewBox="0 0 34 34" height="34" width="34" preserveAspectRatio="xMidYMid meet" class="" version="1.1" x="0px" y="0px" enable-background="new 0 0 34 34"><path fill="currentColor" d="M9.2,25c0,0.5,0.4,1,0.9,1h3.6c0.5,0,0.9-0.4,0.9-1V9c0-0.5-0.4-0.9-0.9-0.9h-3.6 C9.7,8,9.2,8.4,9.2,9V25z M20.2,8c-0.5,0-1,0.4-1,0.9V25c0,0.5,0.4,1,1,1h3.6c0.5,0,1-0.4,1-1V9c0-0.5-0.4-0.9-1-0.9 C23.8,8,20.2,8,20.2,8z"></path></svg>
</button>
</div>
<div class="audio_wave_section">
<div id="waveform${uniqueId}" class="audio_wave_list"></div>
<div class="audio_bottom">
<span id="text${uniqueId}" class="show_duration show_d_${uniqueId}"></span>
<span class="chatbox-message-item-time">${time} <span class="message-view-svg">
<svg class="tickMessage d-none" viewBox="0 0 16 11" height="11" width="16" preserveAspectRatio="xMidYMid meet" class="" fill="none"><path d="M11.0714 0.652832C10.991 0.585124 10.8894 0.55127 10.7667 0.55127C10.6186 0.55127 10.4916 0.610514 10.3858 0.729004L4.19688 8.36523L1.79112 6.09277C1.7488 6.04622 1.69802 6.01025 1.63877 5.98486C1.57953 5.95947 1.51817 5.94678 1.45469 5.94678C1.32351 5.94678 1.20925 5.99544 1.11192 6.09277L0.800883 6.40381C0.707784 6.49268 0.661235 6.60482 0.661235 6.74023C0.661235 6.87565 0.707784 6.98991 0.800883 7.08301L3.79698 10.0791C3.94509 10.2145 4.11224 10.2822 4.29844 10.2822C4.40424 10.2822 4.5058 10.259 4.60313 10.2124C4.70046 10.1659 4.78086 10.1003 4.84434 10.0156L11.4903 1.59863C11.5623 1.5013 11.5982 1.40186 11.5982 1.30029C11.5982 1.14372 11.5348 1.01888 11.4078 0.925781L11.0714 0.652832ZM8.6212 8.32715C8.43077 8.20866 8.2488 8.09017 8.0753 7.97168C7.99489 7.89128 7.8891 7.85107 7.75791 7.85107C7.6098 7.85107 7.4892 7.90397 7.3961 8.00977L7.10411 8.33984C7.01947 8.43717 6.97715 8.54508 6.97715 8.66357C6.97715 8.79476 7.0237 8.90902 7.1168 9.00635L8.1959 10.0791C8.33132 10.2145 8.49636 10.2822 8.69102 10.2822C8.79681 10.2822 8.89838 10.259 8.99571 10.2124C9.09304 10.1659 9.17556 10.1003 9.24327 10.0156L15.8639 1.62402C15.9358 1.53939 15.9718 1.43994 15.9718 1.32568C15.9718 1.1818 15.9125 1.05697 15.794 0.951172L15.4386 0.678223C15.3582 0.610514 15.2587 0.57666 15.1402 0.57666C14.9964 0.57666 14.8715 0.635905 14.7657 0.754395L8.6212 8.32715Z" fill="currentColor"></path></svg>
<svg class="waitTime" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M464 256A208 208 0 1 1 48 256a208 208 0 1 1 416 0zM0 256a256 256 0 1 0 512 0A256 256 0 1 0 0 256zM232 120l0 136c0 8 4 15.5 10.7 20l96 64c11 7.4 25.9 4.4 33.3-6.7s4.4-25.9-6.7-33.3L280 243.2 280 120c0-13.3-10.7-24-24-24s-24 10.7-24 24z"/></svg></span></span>
</div>
</div>
</div>
</span>
</div>
<div class="more-info-dropdown">
<ul>
<li>
<a href="" class="DownloadAttachment" download="">
<span class="ms-info-svg"><svg viewBox="0 0 24 24" height="24" width="24" preserveAspectRatio="xMidYMid meet" class="" fill="none"><path d="M12.3536 15.6464C12.1583 15.8417 11.8417 15.8417 11.6464 15.6464L7.69481 11.6948C7.30912 11.3091 7.30886 10.6801 7.68772 10.2877C8.0761 9.88547 8.72424 9.87424 9.11962 10.2696L11 12.15V5C11 4.44772 11.4477 4 12 4C12.5523 4 13 4.44772 13 5V12.15L14.8804 10.2696C15.2758 9.87424 15.9239 9.88547 16.3123 10.2877C16.6911 10.6801 16.6909 11.3091 16.3052 11.6948L12.3536 15.6464ZM6 20C5.45 20 4.97917 19.8042 4.5875 19.4125C4.19583 19.0208 4 18.55 4 18V16C4 15.4477 4.44772 15 5 15C5.55228 15 6 15.4477 6 16V18H18V16C18 15.4477 18.4477 15 19 15C19.5523 15 20 15.4477 20 16V18C20 18.55 19.8042 19.0208 19.4125 19.4125C19.0208 19.8042 18.55 20 18 20H6Z" fill="currentColor"></path></svg></span>
<span>Download</span>
</a>
</li>
</ul>
</div>
</div>
</div>
`);
chatBox.scrollTop = chatBox.scrollHeight;
}
// get Audio File opposite users
function getAttachmentAudioFileOppositeUsers (uniqueId,item, url) {
let time = new Date(item.created_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
const now = new Date(item.created_at);
let shortName = getShortName(item.name)
let name = item.name || item.senderName
// let uniqueId = 'id-'
// + now.getFullYear()
// + (now.getMonth() + 1).toString().padStart(2, '0')
// + now.getDate().toString().padStart(2, '0')
// + now.getHours().toString().padStart(2, '0')
// + now.getMinutes().toString().padStart(2, '0')
// + now.getSeconds().toString().padStart(2, '0')
// + now.getMilliseconds().toString().padStart(3, '0')
// + '-' + Math.floor(Math.random() * 10000);
let imageHtml = item.profileimage ? `<img src="${item.profileimage}" border-radius: 50px;">` : `${shortName}`;
const chatBox = document.getElementById("chatBox");
chatBox.innerHTML +=`<div class="chatBoxListMessage received audioAttachment" data-audio-url="${url}">
<div class="chatbox-message-item received">
<span class="profileimage1 ${item.profileimage ? "border-none":""}">${imageHtml}</span>
<span class="p0s8C">
<svg viewBox="0 0 8 13" height="13" width="8" preserveAspectRatio="xMidYMid meet" class="" version="1.1" x="0px" y="0px" enable-background="new 0 0 8 13" xml:space="preserve">
<path opacity="0.13" fill="#0000000"
d="M1.533,3.568L8,12.193V1H2.812 C1.042,1,0.474,2.156,1.533,3.568z">
</path>
<path fill="currentColor" d="M1.533,2.568L8,11.193V0L2.812,0C1.042,0,0.474,1.156,1.533,2.568z"></path>
</svg>
</span>
<div class="sentMessagesBox">
<button type="button" class="optionMessage">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M201.4 374.6c12.5 12.5 32.8 12.5 45.3 0l160-160c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L224 306.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l160 160z"/></svg>
</button>
<span class="chatbox-message-item-text">
<span class="receivedName">${name} <p>(${item.userRole == '1'
? 'Superadmin'
: item.userRole == '3'
? 'Hospital Admin'
: item.userRole == '4'
? 'Hospital Supervisor'
: item.userRole == '5'
? 'Hospital Agent'
: item.userRole == '6'
? 'Super Admin Supervisor'
: item.userRole == '7'
? 'Super Admin Agent'
: item.userRole == '8'
? 'Diognostic Admin'
: item.userRole == '9'
? 'Diognostic Supervisor'
: item.userRole == '10'
? 'Diognostic Agent'
: 'Unknown'})</p></span>
<div class="audio_file_section">
<div class="audio_button_playPause">
<button class="playPauseBtn" id="playPause${uniqueId}">
<svg class="playA" viewBox="0 0 34 34" height="34" width="34" preserveAspectRatio="xMidYMid meet" class="" version="1.1" x="0px" y="0px" enable-background="new 0 0 34 34"><path fill="currentColor" d="M8.5,8.7c0-1.7,1.2-2.4,2.6-1.5l14.4,8.3c1.4,0.8,1.4,2.2,0,3l-14.4,8.3 c-1.4,0.8-2.6,0.2-2.6-1.5V8.7z"></path></svg>
<svg class="pauseA" viewBox="0 0 34 34" height="34" width="34" preserveAspectRatio="xMidYMid meet" class="" version="1.1" x="0px" y="0px" enable-background="new 0 0 34 34"><path fill="currentColor" d="M9.2,25c0,0.5,0.4,1,0.9,1h3.6c0.5,0,0.9-0.4,0.9-1V9c0-0.5-0.4-0.9-0.9-0.9h-3.6 C9.7,8,9.2,8.4,9.2,9V25z M20.2,8c-0.5,0-1,0.4-1,0.9V25c0,0.5,0.4,1,1,1h3.6c0.5,0,1-0.4,1-1V9c0-0.5-0.4-0.9-1-0.9 C23.8,8,20.2,8,20.2,8z"></path></svg>
</button>
</div>
<div class="audio_wave_section">
<div id="waveform${uniqueId}" class="audio_wave_list"></div>
<div class="audio_bottom">
<span id="text${uniqueId}" class="show_duration show_d_${uniqueId}"></span>
<span class="chatbox-message-item-time">${time} </span>
</div>
</div>
</div>
</span>
</div>
<div class="more-info-dropdown">
<ul>
<li>
<a href="${url}" class="DownloadAttachment" download="">
<span class="ms-info-svg"><svg viewBox="0 0 24 24" height="24" width="24" preserveAspectRatio="xMidYMid meet" class="" fill="none"><path d="M12.3536 15.6464C12.1583 15.8417 11.8417 15.8417 11.6464 15.6464L7.69481 11.6948C7.30912 11.3091 7.30886 10.6801 7.68772 10.2877C8.0761 9.88547 8.72424 9.87424 9.11962 10.2696L11 12.15V5C11 4.44772 11.4477 4 12 4C12.5523 4 13 4.44772 13 5V12.15L14.8804 10.2696C15.2758 9.87424 15.9239 9.88547 16.3123 10.2877C16.6911 10.6801 16.6909 11.3091 16.3052 11.6948L12.3536 15.6464ZM6 20C5.45 20 4.97917 19.8042 4.5875 19.4125C4.19583 19.0208 4 18.55 4 18V16C4 15.4477 4.44772 15 5 15C5.55228 15 6 15.4477 6 16V18H18V16C18 15.4477 18.4477 15 19 15C19.5523 15 20 15.4477 20 16V18C20 18.55 19.8042 19.0208 19.4125 19.4125C19.0208 19.8042 18.55 20 18 20H6Z" fill="currentColor"></path></svg></span>
<span>Download</span>
</a>
</li>
</ul>
</div>
</div>
</div>
`;
chatBox.scrollTop = chatBox.scrollHeight;
}
let uniqueId = 'id-'
+ now.getFullYear()
+ (now.getMonth() + 1).toString().padStart(2, '0')
+ now.getDate().toString().padStart(2, '0')
+ now.getHours().toString().padStart(2, '0')
+ now.getMinutes().toString().padStart(2, '0')
+ now.getSeconds().toString().padStart(2, '0')
+ now.getMilliseconds().toString().padStart(3, '0')
+ index
+ '-' + Math.floor(Math.random() * 10000);
and im creating unique id using date