Home  /  Blog  /  Quran Audio Player

Build a Quran Audio Player with HTML5 and JavaScript

Tutorial8 min readJuly 2026

You will build a working Quran audio player from scratch. It loads surahs from UmmahAPI, plays each ayah in sequence, and lets the user switch reciters on the fly using the HTML5 Audio API.

What You Will Build

The player will have four parts. A reciter dropdown with eight options. A surah picker that loads all 114 surahs from the API. An ayah-by-ayah playback loop that advances automatically. And a simple progress bar using the HTML5 Audio object's timeupdate event.

No frameworks needed. No build tools. Just HTML, CSS, and JavaScript. You can drop this into any existing project, including a React or Vue app, but this tutorial keeps it vanilla so the concepts are clear.

The full Quran API reference has everything we use here, and the API docs cover all available query parameters.

How UmmahAPI Returns Audio URLs

UmmahAPI does not have a separate audio endpoint. Audio URLs come back as part of the ayah data. You just add ?reciter=alafasy (or any supported reciter) to your request. The response includes an audio field on each ayah object.

Here is a quick fetch to see what one ayah looks like with audio data:

const res = await fetch(
  'https://ummahapi.com/api/quran/surah/1/ayah/1?reciter=alafasy'
);
const data = await res.json();

console.log(data.ayah.arabic);  // Arabic text
console.log(data.ayah.audio);   // Audio URL for this ayah
console.log(data.ayah.translations.en); // English translation

The eight supported reciters are: alafasy, abdul_basit, sudais, husary, ajamy, shaatri, muaiqly, and dosari. Each one returns a different audio URL for the same ayah.

Tip. Fetching a full surah with ?reciter=alafasy gives you audio URLs for every ayah in one request. This is much better than fetching each ayah one by one. Load the surah first, store the ayah array, then play them in order.

Step 1: Load the Surah List

Start by fetching all 114 surahs and populating a <select> element. The /api/quran/surahs endpoint returns the name, number, and ayah count for each surah.

async function loadSurahs(selectEl) {
  const res = await fetch('https://ummahapi.com/api/quran/surahs');
  const data = await res.json();

  data.surahs.forEach(surah => {
    const opt = document.createElement('option');
    opt.value = surah.number;
    opt.textContent = `${surah.number}. ${surah.name_english} (${surah.ayahs_count} ayahs)`;
    selectEl.appendChild(opt);
  });
}

const surahSelect = document.getElementById('surah-select');
loadSurahs(surahSelect);

Each option value is the surah number. We will use that number to fetch the full surah with audio data when the user presses play.

Step 2: Fetch the Full Surah with Audio

When the user picks a surah and a reciter, fetch the full surah. One request, all ayahs, all audio URLs. Store the ayah array and set a current index to zero.

let ayahs = [];
let currentIndex = 0;
let audioPlayer = new Audio();

async function loadSurah(surahNumber, reciter) {
  const url = `https://ummahapi.com/api/quran/surah/${surahNumber}?reciter=${reciter}&translations=en`;
  const res = await fetch(url);
  const data = await res.json();

  ayahs = data.surah.ayahs;
  currentIndex = 0;

  return ayahs;
}

// Example: Surah Al-Fatiha with Mishary Alafasy
loadSurah(1, 'alafasy').then(ayahs => {
  console.log(`Loaded ${ayahs.length} ayahs`);
  console.log(ayahs[0].audio); // Audio URL for ayah 1
});

Step 3: Play Ayahs One by One

This is the core of the player. Set the src of your Audio object to the current ayah's audio URL, call play(), and then listen for the ended event to move to the next ayah.

function playAyah(index) {
  if (index >= ayahs.length) {
    updateStatus('Surah complete.');
    return;
  }

  const ayah = ayahs[index];
  currentIndex = index;

  // Set audio source and play
  audioPlayer.src = ayah.audio;
  audioPlayer.play();

  // Update the display
  document.getElementById('ayah-arabic').textContent = ayah.arabic;
  document.getElementById('ayah-translation').textContent = ayah.translations?.en || '';
  updateStatus(`Playing ayah ${ayah.number_in_surah} of ${ayahs.length}`);
}

// Auto-advance when an ayah finishes
audioPlayer.addEventListener('ended', () => {
  playAyah(currentIndex + 1);
});

// Update progress bar
audioPlayer.addEventListener('timeupdate', () => {
  const progress = document.getElementById('progress');
  if (audioPlayer.duration) {
    progress.value = (audioPlayer.currentTime / audioPlayer.duration) * 100;
  }
});

The ended event is what makes the ayah-by-ayah loop work. When one ayah finishes, the handler calls playAyah with the next index. When you hit the last ayah, it stops and shows a status message.

Tip. The Audio.play() method returns a Promise. Browsers block autoplay if the user has not interacted with the page yet. Always trigger play() from a button click or other user gesture to avoid NotAllowedError.

Step 4: Reciter Switching

Switching reciters means re-fetching the same surah with a different ?reciter= value. Stop the current audio first, then reload the surah and restart from the same ayah index so the user does not lose their place.

async function switchReciter(newReciter) {
  const surahNumber = document.getElementById('surah-select').value;
  const savedIndex = currentIndex;
  const wasPlaying = !audioPlayer.paused;

  // Stop current playback
  audioPlayer.pause();
  audioPlayer.src = '';

  updateStatus('Switching reciter...');

  await loadSurah(surahNumber, newReciter);

  // Resume from same position
  if (wasPlaying) {
    playAyah(savedIndex);
  } else {
    currentIndex = savedIndex;
    document.getElementById('ayah-arabic').textContent = ayahs[savedIndex].arabic;
    document.getElementById('ayah-translation').textContent = ayahs[savedIndex].translations?.en || '';
    updateStatus(`Ready. Ayah ${savedIndex + 1} of ${ayahs.length}`);
  }
}

document.getElementById('reciter-select').addEventListener('change', (e) => {
  switchReciter(e.target.value);
});

Step 5: Putting the HTML Together

Here is the full HTML structure the JavaScript expects. Keep it simple. Two dropdowns, a progress bar, four control buttons, and two display areas for the Arabic text and translation.

<div id="quran-player">

  <div class="selects">
    <div>
      <label for="reciter-select">Reciter</label>
      <select id="reciter-select">
        <option value="alafasy">Mishary Alafasy</option>
        <option value="abdul_basit">Abdul Basit</option>
        <option value="sudais">Abdul Rahman Al-Sudais</option>
        <option value="husary">Mahmoud Khalil Al-Husary</option>
        <option value="ajamy">Ahmad Al-Ajamy</option>
        <option value="shaatri">Abu Bakr Al-Shaatri</option>
        <option value="muaiqly">Maher Al-Muaiqly</option>
        <option value="dosari">Yasser Al-Dosari</option>
      </select>
    </div>
    <div>
      <label for="surah-select">Surah</label>
      <select id="surah-select"></select>
    </div>
  </div>

  <div class="controls">
    <button id="btn-prev">Prev</button>
    <button id="btn-play">Play</button>
    <button id="btn-pause">Pause</button>
    <button id="btn-next">Next</button>
  </div>

  <div class="progress-wrap">
    <input type="range" id="progress" min="0" max="100" value="0">
    <div class="progress-times">
      <span id="time-current">0:00</span>
      <span id="time-total">0:00</span>
    </div>
  </div>

  <div id="ayah-arabic"></div>
  <div id="ayah-translation"></div>
  <div id="player-status"></div>

</div>

Step 6: Wire Up the Buttons

The play button fetches the surah if no ayahs are loaded yet, then starts playback. The pause button stops the audio. Previous and next buttons jump one ayah in either direction.

function updateStatus(msg) {
  document.getElementById('player-status').textContent = msg;
}

function formatTime(seconds) {
  const m = Math.floor(seconds / 60);
  const s = Math.floor(seconds % 60).toString().padStart(2, '0');
  return `${m}:${s}`;
}

audioPlayer.addEventListener('timeupdate', () => {
  document.getElementById('time-current').textContent = formatTime(audioPlayer.currentTime);
  document.getElementById('time-total').textContent = formatTime(audioPlayer.duration || 0);
});

document.getElementById('btn-play').addEventListener('click', async () => {
  const surahNumber = document.getElementById('surah-select').value;
  const reciter = document.getElementById('reciter-select').value;

  if (ayahs.length === 0) {
    updateStatus('Loading surah...');
    await loadSurah(surahNumber, reciter);
  }

  if (audioPlayer.paused) {
    if (audioPlayer.src) {
      audioPlayer.play(); // Resume
    } else {
      playAyah(currentIndex); // Start from current
    }
  }
});

document.getElementById('btn-pause').addEventListener('click', () => {
  audioPlayer.pause();
  updateStatus('Paused.');
});

document.getElementById('btn-next').addEventListener('click', () => {
  if (currentIndex + 1 < ayahs.length) {
    playAyah(currentIndex + 1);
  }
});

document.getElementById('btn-prev').addEventListener('click', () => {
  if (currentIndex > 0) {
    playAyah(currentIndex - 1);
  }
});

// Seek when user drags the progress bar
document.getElementById('progress').addEventListener('input', (e) => {
  if (audioPlayer.duration) {
    audioPlayer.currentTime = (e.target.value / 100) * audioPlayer.duration;
  }
});

// When surah selection changes, reset everything
document.getElementById('surah-select').addEventListener('change', () => {
  audioPlayer.pause();
  audioPlayer.src = '';
  ayahs = [];
  currentIndex = 0;
  document.getElementById('ayah-arabic').textContent = '';
  document.getElementById('ayah-translation').textContent = '';
  document.getElementById('progress').value = 0;
  updateStatus('Press Play to start.');
});

The seek bar input handler maps the 0-100 range back to seconds using audio.duration. This lets the user scrub within the current ayah. Seeking across ayahs is not supported here, but you could extend it by calculating cumulative durations if you want that feature.

Live Demo

Here is the player running directly on this page. Pick a reciter and surah, then press Play.

0:00 0:00
Select a surah and press Play.

A Note on CORS and Hosting

UmmahAPI is open to browser requests. No proxy needed. The audio files linked in the API responses are served from external CDNs, and most of them allow cross-origin playback. If a specific audio URL does not play in the browser due to CORS restrictions, check the browser console for a CORS error and consider proxying that request through your own server.

For production use, register for a free API key at ummahapi.com/register. This removes the 5,000 request per 15 minute limit and lets you identify your app in the dashboard.

FAQ

How do I play Quran audio in JavaScript?

Use the HTML5 Audio API. Create a new Audio() object, assign a URL to its src property, and call .play(). UmmahAPI returns an audio URL for each ayah when you include the ?reciter= query parameter on any Quran endpoint. Make sure play() is triggered by a user action like a button click, or the browser will block it.

Which reciters does UmmahAPI support?

Eight reciters are available: alafasy, abdul_basit, sudais, husary, ajamy, shaatri, muaiqly, and dosari. Pass any of these as the value for the reciter query parameter. You will get back audio URLs specific to that reciter for each ayah.

Can I play Quran ayahs one by one with the HTML5 Audio API?

Yes. Fetch the full surah once to get all ayah audio URLs in one request. Store them in an array. Play the first ayah, then listen for the ended event on the Audio object and call your play function with the next index. This creates a smooth, automatic sequence through all the ayahs of any surah.

Do I need an API key to use UmmahAPI for a Quran audio player?

No. You can start without a key and get up to 5,000 requests per 15 minutes. That is more than enough for development and small projects. For a production app with real users, grab a free key at ummahapi.com/register to get unlimited requests and access to usage stats.

Start building with UmmahAPI

Free Quran, Prayer Times, Hadith, and Duas API for developers, with no credit card required.

Read the docs