-1

I want to use 6 of these Before/After Audio Players on my website: https://github.com/mattbartley/AB-Audio-Player

However, the sources of the audio files are set in the JavaScript code. So when I implement multiple instances of this player in my HTML, they all play the same two audio files. I want to set data attributes in the HTML code in each player div for the src of the files and let the JavaScript use that instead. How do I go about it?

<div class="player__wrapper" data-audio-before="Song-1-before.mp3" data-audio-after="Song-1-after.mp3">

...

<div class="player__wrapper" data-audio-before="Song-2-before.mp3" data-audio-after="Song-2-after.mp3">

...

And so on. This would be the updated HTML code.

I think it's a pretty basic solution, I'm just not good in JavaScript.

Here is the current JavaScript code:

//Set up audio elements
var soundA = document.createElement("audio");
//Set audio A src here
soundA.src = "./assets/a.mp3";
soundA.preload = "auto";
soundA.setAttribute("hidden", "true");
soundA.setAttribute("onplaying", "stepA()");
document.body.append(soundA);

var soundB = document.createElement("audio");
//Set audio B src here
soundB.src = "./assets/b.mp3";
soundB.preload = "auto";
soundB.setAttribute("hidden", "true");
soundB.setAttribute("onplaying", "stepB()");
document.body.append(soundB);

I tried something like this: Hover over div, use its data-attribute to change the src of an img And other approaches as well. Couldn't get it to work.

4
  • Perhaps changing the source of the ab-player.js would be a better way to go. For example, you could wrap the entire source into a function, and the function would take an element as its argument. You would then set the soundA.src and soundB.src to whatever the value of data-audio-before and data-audio-after are. You'd also need to adapt the getElementById parts to match the appropriate players (you could get them by class, or with querySelector). Commented Jan 29, 2023 at 13:25
  • Thank you for your reply! How would the function be? Something like function source() { const player = document.querySelectorAll('.player__wrapper') soundA.src = player.dataset.dataAudioBefore; soundB.src = player.dataset.dataAudioAfter; }; source();? And why would I need to change the getElementById parts? They are just for the button elements and the progress bar. Maybe I misunderstood you. Commented Jan 30, 2023 at 7:06
  • The original script heavily relies on unique IDs for triggering sound A+B. If you want to place multiple instances, you need to rewrite the script completely - most importantly by selections relative to the current player wrap. You should concider switching to another script with similar functionality Commented Jan 30, 2023 at 18:00
  • 1
    Something like that, yes, but the function would take your <div class="player__wrapper"> as an argument, and work with it. You would also need to rearrange the HTML elements a bit, so that the controls are within that specific div, and you would collect them not with document.getElementById, but with <your_player_wrapper_used_as_function_argument>.querySelector(<class_of_specific_player_control). You would also need to reconfigure the way playSoundA and playSoundB are working - they would need arguments as well. And so on. Commented Jan 30, 2023 at 18:01

4 Answers 4

0

I forked the Matt Bartley 's AB-Audio-Player - not great but it works with multiple instances:

As commented, the player initialization is done within a forEach loop.
All elements are selected by class names to avoid non unique IDs and relative element selection.

So you need to update your HTML template accordingly.

let players = document.querySelectorAll(".player__wrapper");
initPlayers(players);

function initPlayers(players) {
  players.forEach((player) => {
    //Get button elements
    const playBtns = player.querySelectorAll(".ab__button");
    const aButton = player.querySelector(".a__button");
    const bButton = player.querySelector(".b__button");
    const playButton = player.querySelector(".play__button");
    const stopButton = player.querySelector(".stop__button");
    const progressBar = player.querySelector(".progress__bar");
    const progressFill = player.querySelector(".progress__fill");

    // set icons
    const playIcon = '<i class="fa-solid fa-play"></i>';
    const pauseIcon = '<i class="fa-solid fa-pause"></i>';
    const stopIcon = '<i class="fa-solid fa-stop"></i>';

    //Default loading state for each sound
    var soundAReady = false;
    var soundBReady = false;

    //Set up audio elements
    var soundA = document.createElement("audio");
    soundA.src = player.getAttribute("data-sound-a");
    soundA.preload = "auto";
    soundA.setAttribute("hidden", "true");
    player.append(soundA);

    var soundB = document.createElement("audio");
    soundB.src = player.getAttribute("data-sound-b");
    soundB.preload = "auto";
    soundB.setAttribute("hidden", "true");
    player.append(soundB);

    //playSoundA
    aButton.addEventListener("click", (e) => {
      pauseAll();
      playButton.innerHTML = pauseIcon;
      aButton.disabled = true;
      bButton.disabled = false;
      stopButton.disabled = false;
      soundA.currentTime = soundB.currentTime;
      soundA.play();
    });

    //playSoundB
    bButton.addEventListener("click", (e) => {
      pauseAll();
      playButton.innerHTML = pauseIcon;
      bButton.disabled = true;
      aButton.disabled = false;
      stopButton.disabled = false;
      soundB.currentTime = soundA.currentTime;
      soundB.play();
    });

    //playSoundA
    soundA.addEventListener("playing", (e) => {
      console.log("playing");
      progressFill.style.width =
        ((soundA.currentTime / soundA.duration) * 100 || 0) + "%";
      requestAnimationFrame(stepA);
    });

    //playSoundB
    soundB.addEventListener("playing", (e) => {
      console.log("playing B");
      progressFill.style.width =
        ((soundB.currentTime / soundB.duration) * 100 || 0) + "%";
      requestAnimationFrame(stepB);
    });

    // playPause
    playButton.addEventListener("click", (e) => {
      if (soundA.paused & soundB.paused) {
        let soundATime = soundA.currentTime;
        let soundBTime = soundB.currentTime;
        if (soundATime >= soundBTime) {
          soundA.play();
          bButton.disabled = false;
          aButton.disabled = true;
          playButton.innerHTML = pauseIcon;
        } else {
          soundB.play();
          bButton.disabled = true;
          aButton.disabled = false;
          playButton.innerHTML = pauseIcon;
        }
        stopButton.disabled = false;
      } else {
        playButton.innerHTML = playIcon;
        soundA.pause();
        soundB.pause();
      }
    });

    // stop
    stopButton.addEventListener("click", (e) => {
      playButton.innerHTML = playIcon;
      aButton.disabled = false;
      bButton.disabled = true;
      playButton.disabled = false;
      stopButton.disabled = true;
      soundA.pause();
      soundA.currentTime = 0;
      soundB.pause();
      soundB.currentTime = 0;
    });

    //Check for mobile to enable audio playback without waiting for download status.
    if (
      /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
        navigator.userAgent
      )
    ) {
      playButton.disabled = false;
    }

    //Default loading state for each sound
    var soundAReady = false;
    var soundBReady = false;

    //When audio can play through (loaded), run the function to enable buttons
    //The canplaythrough event will fire every time the audio switches, so the !soundA/BReady
    //prevents additional checks
    soundA.oncanplaythrough = function() {
      if (!soundAReady) {
        soundAReady = true;
        audioIsReady();
      }
    };
    soundB.oncanplaythrough = function() {
      if (!soundBReady) {
        soundBReady = true;
        audioIsReady();
      }
    };

    // Check if both A & B are ready and enable the correct buttons
    function audioIsReady() {
      if (soundAReady && soundBReady) {
        console.log("...audio loaded!");
        aButton.disabled = false;
        playButton.disabled = false;
      } else {
        console.log("Audio loading...");
      }
    }

    const progress = player.querySelector(".progress");
    // Listen for click on entire progress bar div (to allow skipping ahead)
    progress.addEventListener("click", function(event) {
      // Get X coordinate of click in div
      var rect = this.getBoundingClientRect();
      // Convert click position to percentage value
      var percentage = (event.clientX - rect.left) / this.offsetWidth;
      // Seek to the percentage converted to seconds
      soundA.currentTime = percentage * soundA.duration;
      soundB.currentTime = percentage * soundB.duration;
    });

    //Frame animations for progress bar fill - converts to CSS percentage
    function stepA() {
      progressFill.style.width =
        ((soundA.currentTime / soundA.duration) * 100 || 0) + "%";
      requestAnimationFrame(stepA);
    }

    function stepB() {
      progressFill.style.width =
        ((soundB.currentTime / soundB.duration) * 100 || 0) + "%";
      requestAnimationFrame(stepB);
    }

    //Play/Stop correct audio and toggle A/B, Play/Pause, and Stop buttons
    function playPause() {
      if (soundA.paused & soundB.paused) {
        let soundATime = soundA.currentTime;
        let soundBTime = soundB.currentTime;
        if (soundATime >= soundBTime) {
          soundA.play();
          bButton.disabled = false;
          aButton.disabled = true;
          playButton.innerHTML = pauseIcon;
        } else {
          soundB.play();
          bButton.disabled = true;
          aButton.disabled = false;
          playButton.innerHTML = pauseIcon;
        }
        stopButton.disabled = false;
      } else {
        playButton.innerHTML = playIcon;
        soundA.pause();
        soundB.pause();
      }
    }

    // optional: set auto ids
    let allAudio = document.querySelectorAll("audio");
    allAudio.forEach((audio, i) => {
      audio.id = "audio_" + i;
    });

    // rewind all at end
    allAudio.forEach((audio) => {
      //audio.pause();
      audio.addEventListener("ended", (e) => {
        audio.currentTime = 0;
        progressFill.style.width = "0%";
      });
    });


    function pauseAll() {
      let allAudio = document.querySelectorAll("audio");
      allAudio.forEach((audio) => {
        audio.pause();
      });
    }



  });
}
<link href="https://fonts.googleapis.com/css2?family=Lato:wght@400;700&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css" integrity="sha512-KfkfwYDsLkIlwQp6LFnl8zNdLGxu9YAA1QvwINks4PhcElQSvqcyVLLD9aMhXd13uQjoXtEKNosOWaZqXgel0g==" crossorigin="anonymous" referrerpolicy="no-referrer"
/>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/mattbartley/AB-Audio-Player@main/css/style.css" />

<div class="player__wrapper" data-sound-a="https://cdn.jsdelivr.net/gh/mattbartley/AB-Audio-Player@main/assets/a.mp3" data-sound-b="https://cdn.jsdelivr.net/gh/mattbartley/AB-Audio-Player@main/assets/b.mp3">
  <div class="progress__container progress">
    <div class="progress__bar progress__fill"></div>
  </div>
  <div class="ab__controls">
    <button class="ab__button a__button" disabled="true">
          A
        </button>
    <button class="ab__button b__button" disabled="true">
          B
        </button>
  </div>
  <div class="play__stop__controls">
    <button class="play__pause__button play__button" disabled="true">
          <i class="fa-solid fa-play"></i>
        </button>
    <button class="play__pause__button stop__button" disabled="true">
          <i class="fa-solid fa-stop"></i>
        </button>
  </div>
</div>


<div class="player__wrapper" data-sound-a="https://cdn.jsdelivr.net/gh/mattbartley/AB-Audio-Player@main/assets/a.mp3" data-sound-b="https://cdn.jsdelivr.net/gh/mattbartley/AB-Audio-Player@main/assets/b.mp3">
  <div class="progress__container progress">
    <div class="progress__bar progress__fill"></div>
  </div>
  <div class="ab__controls">
    <button class="ab__button a__button" disabled="true">
          A
        </button>
    <button class="ab__button b__button" disabled="true">
          B
        </button>
  </div>
  <div class="play__stop__controls">
    <button class="play__pause__button play__button" disabled="true">
          <i class="fa-solid fa-play"></i>
        </button>
    <button class="play__pause__button stop__button" disabled="true">
          <i class="fa-solid fa-stop"></i>
        </button>
  </div>
</div>

Sign up to request clarification or add additional context in comments.

3 Comments

Awesome, that worked, thank you so much! Do you know how to implement that when one player starts, the other one is stopped (otherwise both can play at the same time)? And how to fix the issue that the progress bar only fills when "A" is active? I would be forever grateful.
Check the revised example
Thanks! The progress bar works but both players can still play at the same time
0

According to MDN, firstly you should change your HTML a bit and add id to your elements:

<div id="player-1" class="player__wrapper" data-audio-before="Song-1-before.mp3" data-audio-after="Song-1-after.mp3">

Then you can access your data-audio attribute from your JS code thorough dataset object:

//Set up audio elements
var soundA = document.createElement("audio");

//bring div element to your code
const playerA = document.querySelector("#player-1")

//Set audio A src here
soundA.src = playerA.dataset.dataAudioBefore;

soundA.preload = "auto";
soundA.setAttribute("hidden", "true");
soundA.setAttribute("onplaying", "stepA()");
document.body.append(soundA);

var soundB = document.createElement("audio");

//Set audio B src here
soundB.src = playerA.dataset.dataAudioAfter;

soundB.preload = "auto";
soundB.setAttribute("hidden", "true");
soundB.setAttribute("onplaying", "stepB()");
document.body.append(soundB);

hope this helps!

1 Comment

Thank you for your reply! This didn't work unfortunately :-(
0

This is actually a comment on your answer, but duo to StackOverflow policy, I couldn't comment directly!

Anyway, I think your problem is that you are using querySelectorAll with classes. Have you tried using id in your HTML element and using querySelector("#id") in your JS code?

In addition, please vote me up/check correct answer if it helps!

9 Comments

Yes I tried that, but how do I scale it to 6 players then?
Create six div with six different id. Each div consists of two audios, right? Then for each div, write the same JS code but with different id for querySelector. This way you'll have six players and each of them plays two different song. I hope I get you right!
I would still need to frame it in such a way that soundA.src and soundB.src were different for every player. How can I do that? Can I do it with the forEach() method?
Because I can't write soundA.src = player1.dataset.audioBefore and then soundA.src = player2.dataset.audioBefore. What am I missing?
Ok. Just create new variables. If you already have soundA and soundB, then go for soundC , SoundD, and Sound* for instance,
|
0

I got it working somewhat with some help from ChatGPT:

const players = document.querySelectorAll('.player__wrapper');
var soundA, soundB;
players.forEach(player => {
  soundA = document.createElement("audio");
  soundA.src = player.dataset.audioBefore;
  soundA.preload = "auto";
  soundA.setAttribute("hidden", "true");
  soundA.setAttribute("onplaying", "stepA()");
  document.body.append(soundA);
  soundB = document.createElement("audio");
  soundB.src = player.dataset.audioAfter;
  soundB.preload = "auto";
  soundB.setAttribute("hidden", "true");
  soundB.setAttribute("onplaying", "stepB()");
  document.body.append(soundB);
});

It works when I only have one audio player. When I use two, the first one uses the audio files which I put in the second one and the second one doesn't work at all.

Anyone got an idea? Maybe the rest of the JS code is relevant too, here it is:

//Get button elements
const aButton = document.getElementById("a__button");
const bButton = document.getElementById("b__button");
const playButton = document.getElementById("play__button");
const stopButton = document.getElementById("stop__button");
const progressBar = document.getElementById("progress__bar");
const progressFill = document.getElementById("progress__fill");

const playIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M3 22v-20l18 10-18 10z"/></svg>';
const pauseIcon = '<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd"><path d="M10 24h-6v-24h6v24zm10 0h-6v-24h6v24zm-11-23h-4v22h4v-22zm10 0h-4v22h4v-22z"/></svg>';
const stopIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M2 2h20v20h-20z"/></svg>';

//Check for mobile to enable audio playback without waiting for download status.
if (
  /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
    navigator.userAgent
  )
) {
  playButton.disabled = false;
}

//Default loading state for each sound
var soundAReady = false;
var soundBReady = false;

//When audio can play through (loaded), run the function to enable buttons
//The canplaythrough event will fire every time the audio switches, so the !soundA/BReady
//prevents additional checks
soundA.oncanplaythrough = function () {
  if (!soundAReady) {
    soundAReady = true;
    audioIsReady();
  }
};
soundB.oncanplaythrough = function () {
  if (!soundBReady) {
    soundBReady = true;
    audioIsReady();
  }
};

// Check if both A & B are ready and enable the correct buttons
function audioIsReady() {
  if (soundAReady && soundBReady) {
    console.log("...audio loaded!");
    aButton.disabled = false;
    playButton.disabled = false;
  } else {
    console.log("Audio loading...");
  }
}

const progress = document.getElementById("progress");
// Listen for click on entire progress bar div (to allow skipping ahead)
progress.addEventListener("click", function (event) {
  // Get X coordinate of click in div
  var rect = this.getBoundingClientRect();
  // Convert click position to percentage value
  var percentage = (event.clientX - rect.left) / this.offsetWidth;
  // Seek to the percentage converted to seconds
  soundA.currentTime = percentage * soundA.duration;
  soundB.currentTime = percentage * soundB.duration;
});

//Frame animations for progress bar fill - converts to CSS percentage
function stepA() {
  progressFill.style.width =
    ((soundA.currentTime / soundA.duration) * 100 || 0) + "%";
  requestAnimationFrame(stepA);
}
function stepB() {
  progressFill.style.width =
    ((soundB.currentTime / soundB.duration) * 100 || 0) + "%";
  requestAnimationFrame(stepB);
}

//Play/Stop correct audio and toggle A/B, Play/Pause, and Stop buttons
const playPause = () => {
  if (soundA.paused & soundB.paused) {
    let soundATime = soundA.currentTime;
    let soundBTime = soundB.currentTime;
    if (soundATime >= soundBTime) {
      soundA.play();
      bButton.disabled = false;
      aButton.disabled = true;
      playButton.innerHTML = pauseIcon;
    } else {
      soundB.play();
      bButton.disabled = true;
      aButton.disabled = false;
      playButton.innerHTML = pauseIcon;
    }
    stopButton.disabled = false;
  } else {
    playButton.innerHTML = playIcon;
    soundA.pause();
    soundB.pause();
  }
};

const playSoundA = () => {
  playButton.innerHTML = pauseIcon;
  aButton.disabled = true;
  bButton.disabled = false;
  stopButton.disabled = false;
  if (soundB.currentTime > 0) {
    soundA.currentTime = soundB.currentTime;
    soundA.play();
    soundB.pause();
  } else {
    soundA.play();
    soundB.pause();
  }
};

const playSoundB = () => {
  playButton.innerHTML = pauseIcon;
  bButton.disabled = true;
  aButton.disabled = false;

  stopButton.disabled = false;
  if (soundA.currentTime > 0) {
    soundB.currentTime = soundA.currentTime;
    soundB.play();
    soundA.pause();
  } else {
    soundB.play();
  }
};

const stopSounds = () => {
  playButton.innerHTML = playIcon;
  aButton.disabled = false;
  bButton.disabled = true;
  playButton.disabled = false;
  stopButton.disabled = true;
  soundA.pause();
  soundA.currentTime = 0;
  soundB.pause();
  soundB.currentTime = 0;
};

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.