modifying the appearance of the play button through a JavaScript event without directly altering it

I am currently working on building a music player from scratch using HTML, CSS, and JavaScript only. To store the list of songs, I have created an array named "songs" with details such as song name, file path, and cover image path.

let songs = [
   {songName: "Butter", filePath:"songs/1.mp3",coverPath:'covers/1.png'},
   {songName: "Boy With Luv", filePath:"songs/2.mp3",coverPath:'covers/2.jpeg'},
   {songName: "Dynamite", filePath:"songs/3.mp3",coverPath:'covers/3.jpeg'},
   {songName: "Idol", filePath:"songs/4.mp3",coverPath:'covers/4.png'},
   {songName: "Life Goes On", filePath:"songs/5.mp3",coverPath:'covers/5.jpeg'},
   {songName: "Mic Drop", filePath:"songs/6.mp3",coverPath:'covers/6.jpeg'},
]

Here is how the user interface looks:

https://i.stack.imgur.com/0Uk8l.png

Currently, when changing to the next song using the navigation buttons at the bottom, the song changes but the play icon does not update accordingly. For example, if you switch to the fourth song, the UI still shows the third song playing.

https://i.stack.imgur.com/RFswd.png

I want to modify the styling of the icon when either the next or previous button is clicked. The function has access to the current song's index for reference.

For your convenience, here is the code snippet related to the next button functionality:

//next button
document.getElementById('next').addEventListener('click', (e)=>{
 
 songIndex = (songIndex >=5) ? 0 : songIndex+1;
 audioElement.src = `songs/${songIndex}.mp3`;
 audioElement.currentTime = 0;
 audioElement.play();
 masterPlay.classList.remove('fa-play-circle');
 masterPlay.classList.add('fa-pause-circle');
 mastersongName.innerText = "BTS - "+songs[songIndex].songName;
 masterSideImg.src = songs[songIndex].coverPath;

})

Below is the complete HTML structure of the project:

<!DOCTYPE HTML>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <meta http-equiv="X-UA-Compatible" content="IE=edge">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>Redify - listen music here</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
 <nav>
     <ul>
         <li class="brand"><img src="logo.png" alt="logo">Spotify</li>
         <li>Home</li>
         <li>About</li>
     </ul>
 </nav>

<div class="container">
    <div class="songList">
        <h1>Best of BTS</h1>
        <!-- Songs List Items Go Here -->
    </div>

    <div class="songBanner">
    <!-- Song Banner Content Goes Here -->  
    </div>
</div>

<div class="bottom">
    <!-- Bottom Controls Section -->
</div>
<script src="https://kit.fontawesome.com/06646b7200.js" crossorigin="anonymous"></script>
<script src="script.js"></script>

You can visit the GitHub repository for the full project implementation: https://github.com/mohitm15/Redify

Answer №1

I redesigned the play/pause icon configuration into a separate function:

const ICON_PLAY = 1;
const ICON_STOP= 0;

const handleSongPlayIcon = (target, isPlaying) => {
  if (isPlaying == ICON_PLAY) {
    target.classList.remove("fa-pause-circle");
    target.classList.add("fa-play-circle");
  } else if (isPlaying == ICON_STOP) {
    target.classList.remove("fa-play-circle");
    target.classList.add("fa-pause-circle");
  }
};

and made adjustments to several functions in your script for toggling icons

//Initialising Variables

let songIndex = 0;
let audioElement= new Audio("songs/0.mp3");
let masterPlay = document.getElementById("masterPlay");
let myProgressBar = document.getElementById("myProgressBar");
let gif = document.getElementById("gif");
let songItems = Array.from(document.getElementsByClassName("songItem"));
let songTitle = document.getElementsByClassName("songInfo");
let mastersongName = document.getElementById("mastersongName");
let masterSideImg = document.getElementById("masterSideImg");

// Array of Songs
let songs = [
  { songName: "Butter", filePath: "songs/1.mp3", coverPath: "covers/1.png" },
  {
    songName: "Boy With Luv",
    filePath: "songs/2.mp3",
    coverPath: "covers/2.jpeg",
  },
  { songName: "Dynamite", filePath: "songs/3.mp3", coverPath: "covers/3.jpeg" },
  { songName: "Idol", filePath: "songs/4.mp3", coverPath: "covers/4.png" },
  {
    songName: "Life Goes On",
    filePath: "songs/5.mp3",
    coverPath: "covers/5.jpeg",
  },
  { songName: "Mic Drop", filePath: "songs/6.mp3", coverPath: "covers/6.jpeg" },
];

// Reuse ICON_PLAY and ICON_STOP 
const handleSongPlayIcon = (target, isPlaying) => {
  if (isPlaying == ICON_PLAY) {
    target.classList.remove("fa-pause-circle");
    target.classList.add("fa-play-circle");
  } else if (isPlaying ==ICON_STOP ) {
    target.classList.remove("fa-play-circle");
    target.classList.add("fa-pause-circle");
  }
};

// Loop through each song item
songItems.forEach((item, i) => {
  item.getElementsByTagName("img")[0].src = songs[i].coverPath;
  item.getElementsByClassName("songName")[0].innerText = songs[i].songName;
});

// Click event listener for play/pause functionality
masterPlay.addEventListener("click", () => {
  const allSongsPlayItemElements = document.querySelectorAll(".songItemPlay");

  

  if (audioElement.paused || audioElement.currentTime <= 0) {
    audioElement.play();
    handleSongPlayIcon(masterPlay, ICON_STOP);
    handleSongPlayIcon(allSongsPlayItemElements[songIndex], ICON_STOP);
    gif.style.opacity = 1;
  } else {
    audioElement.pause();
    handleSongPlayIcon(masterPlay, ICON_PLAY);
    handleSongPlayIcon(allSongsPlayItemElements[songIndex], ICON_PLAY);
    gif.style.opacity = 0;
  }
});

// Event listener for updating song progress bar
audioElement.addEventListener("timeupdate", () => {
  progress = parseInt((audioElement.currentTime / audioElement.duration) * 100);
  myProgressBar.value = progress;
});

// Update song playback time on manual change
myProgressBar.addEventListener("change", () => {
  audioElement.currentTime =(myProgressBar.value * audioElement.duration) / 100;
});

// Function to display play symbol for all songs
const makeAllPlay = () => {
  Array.from(document.getElementsByClassName("songItemPlay")).forEach(
    (item) => {
      handleSongPlayIcon(item, ICON_PLAY);
    }
  );
};

// Event listeners for song playing/pausing
let selectedSongIndex;
Array.from(document.getElementsByClassName("songItemPlay")).forEach((item) => {
  item.addEventListener("click", (e) => {
    makeAllPlay();
    songIndex = parseInt(e.target.id);
    
    if (audioElement.paused === true) {
      selectedSongIndex = songIndex;
      audioElement.src = `songs/${songIndex}.mp3`;
      audioElement.currentTime = 0;
      audioElement.play();
      
      handleSongPlayIcon(e.target, ICON_STOP);
      handleSongPlayIcon(masterPlay, ICON_STOP);
      gif.style.opacity = 1;
    } else if (audioElement.paused === false) {
      if (selectedSongIndex === songIndex) {
        audioElement.pause();
        
        handleSongPlayIcon(e.target, ICON_PLAY);
        handleSongPlayIcon(masterPlay, ICON_PLAY);
        gif.style.opacity = 0;
      } else {
        makeAllPlay();
        songIndex = parseInt(e.target.id);
        selectedSongIndex = songIndex;

        audioElement.src = `songs/${songIndex}.mp3`;
        audioElement.currentTime = 0;
        audioElement.play();
        
        handleSongPlayIcon(e.target, ICON_STOP);
        handleSongPlayIcon(masterPlay, ICON_STOP);
        gif.style.opacity = 1;
      }
    }
    
    mastersongName.innerText = "BTS - " + songs[songIndex].songName;
    masterSideImg.src = songs[songIndex].coverPath;

    // Check if song has ended
    audioElement.addEventListener("timeupdate", () => {
      if (audioElement.currentTime === audioElement.duration) {
        makeAllPlay();
        console.log("song Completed");
        songIndex = songIndex >= 5 ? 0 : songIndex + 1;
        audioElement.src = `songs/${songIndex}.mp3`;
        audioElement.currentTime = 0;
        audioElement.play();
        mastersongName.innerText = "BTS - " + songs[songIndex].songName;
        masterSideImg.src = songs[songIndex].coverPath;
        
        const allSongsPlayItemElements = document.querySelectorAll(".songItemPlay");
        handleSongPlayIcon(allSongsPlayItemElements[songIndex], ICON_STOP);
      }
    });
  });
});

// Next button functionality
document.getElementById("next").addEventListener("click", () => {
  const allSongsPlayItemElements = document.querySelectorAll(".songItemPlay");
  
  handleSongPlayIcon(allSongsPlayItemElements[songIndex], ICON_PLAY);

  songIndex = songIndex >= 5 ? 0 : songIndex + 1;
  audioElement.src = `songs/${songIndex}.mp3`;
  audioElement.currentTime = 0;
  audioElement.play();

  handleSongPlayIcon(masterPlay, ICON_STOP);
  mastersongName.innerText = "BTS - " + songs[songIndex].songName;
  masterSideImg.src = songs[songIndex].coverPath;
  
  handleSongPlayIcon(allSongsPlayItemElements[songIndex], ICON_STOP);
});

// Previous button functionality
document.getElementById("previous").addEventListener("click", () => {
  const allSongsPlayItemElements = document.querySelectorAll(".songItemPlay");
  
  handleSongPlayIcon(allSongsPlayItemElements[songIndex], ICON_PLAY);

  songIndex = songIndex <= 0 ? 5 : songIndex - 1;
  audioElement.src = `songs/${songIndex}.mp3`;
  audioElement.currentTime = 0;
  audioElement.play();

  handleSongPlayIcon(masterPlay, ICON_STOP);
  mastersongName.innerText = "BTS - " + songs[songIndex].songName;
  masterSideImg.src = songs[songIndex].coverPath;

  handleSongPlayIcon(allSongsPlayItemElements[songIndex], ICON_STOP);
});

Answer №2

It appears that you may have overlooked calling makeAllPlay(); at the start of the previous/next button click handlers. Additionally, don't forget to update the currently playing song item to display the pause button.

Be sure to include the following code snippet near the beginning of both click handlers (for previous/next buttons):

makeAllPlay();
const itemElem = document.getElementsByClassName("songItemPlay")[songIndex];
itemElem.classList.remove('fa-play-circle');
itemElem.classList.add('fa-pause-circle');

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Executing a function while adjusting a range slider

Having an <input type="range"> element on my website presents a particular challenge. To handle changes in this element, I am using the following function: $("#selector").bind("change", function() { //perform desire ...

Tips for shuffling the sequence of EJS variables

I am currently working on creating a quiz that consists of multiple choice questions. In order to display the Question, Correct Answer, and 3 other wrong options, I am utilizing EJS variables. The format will be similar to the following example: Question: ...

What is the best way to use jQuery to filter data in a table by clicking on a specific table row?

My website contains a table with player names and servers, but I want to make them clickable for filtering purposes. For instance, clicking on a player name should reload the leaderboards to show which servers that player plays on, and clicking on a server ...

Potential issue: Firefox causes distortion in animated stroke-linecap="round" properties

While working on a project on Stack Overflow, I noticed a potential bug in Firefox when animating a line with stroke-linecap="round" and vector-effect="non-scaling-stroke" svg{border:1px solid} path{ animation: draw 1s ease-in; ...

Ways to remove an item from firebase database

Currently, I am exploring ways to delete data stored in the Firebase database specifically under the requests category. Check out this example Below are the functions I have implemented to fetch and manipulate the data: export default { async contactArtis ...

Making AngularJS work with angular-ui bootstrap and ensuring compatibility with IE8

Having trouble getting AngularJS code that utilizes angular-ui bootstrap to function properly in IE 8? Following the guidelines in the AngularJS developer guide on IE didn't seem to solve the issue for me. I inserted the code snippet below into my ind ...

What is the best method to update the accessor value of my react table depending on certain data conditions?

const data = { name:"test1", fclPrice:100, lclPrice:null, total:"50" } and here are the two columns: const Datatable = [ { Header: 'Name', accessor: 'name' }, { Header: 'Price', ac ...

Is there a way to retrieve the io object within the io.sockets.on callback function?

My preference is to not alter my sockets method. I was hoping to be able to utilize the io object within the connected function. Could this be a possibility? function sockets (server) { const io = require('socket.io')(server); io.sockets.on ...

Is the jQuery form plugin not passing any data to Node.js?

Check out the HTML form below: <form id="importForm" enctype="multipart/form-data"> <p> <label for="ownerName">Owner Name<pow class="requiredForm ...

Enhancing the model using Sequelize JS

Currently, I am in the process of developing a basic API using Express and Sequelize JS. Recently, I encountered an issue while attempting to update a record with req.School, but no changes seemed to occur. Despite checking the code below thoroughly, I did ...

Issue: parsing error, only 0 bytes out of 4344 have been successfully parsed on Node.js platform

I've been attempting to utilize an upload program to transfer my files. The specific code I'm using is as follows: app.post('/photos',loadUser, function(req, res) { var post = new Post(); req.form.complete(function(err, fields, fil ...

the order of initialization in angularjs directives with templateUrl

In my current scenario, I am faced with a situation where I need to broadcast an event from one controller and have another directive's controller receive the message. The problem arises because the event is sent immediately upon startup of the contro ...

Sending an array from one page to another with Vue

I'm facing the challenge of passing an array from one page to another. When accessing the next page on form submission using this.$router.push('path'), is there a way for me to also transfer the array so that it can be accessed on the new p ...

The curious case of unusual behavior in jQuery's livequery() method

When attempting to use the jQuery plugin "livequery" to highlight certain words within dynamically generated search results, the highlighting does not appear to work! Strangely, adding an alert() function before executing the code causes the highlighting ...

Having trouble running the script, chrome error with message passing?

I've hit a roadblock while working on my Chrome extension and could use some assistance. The main issue I'm facing is getting the script to run when activated by the user through an on/off switch in the popup window. It seems like there might be ...

How to easily update a URL string in Angular 5 router without altering the state of the application

I am working on an Angular 5 application that utilizes the angular router. The majority of my entry route components are placed under a context id, which represents a name in the app store along with other relevant data for that context. Even though the na ...

Adjust the height of a div using jQuery animate based on the amount of text within

I have created a basic animation feature where users can click on an expanding box. I am looking for a solution that does not involve using a plugin. Currently, the code looks like this: $('#one').css("height", "22"); $('#t1').cli ...

Comparison between Mobile Phone's innerWidth and innerHeight Versus Resolution

My test phone, the Galaxy S3 Mini, has a resolution of 800x480 according to its specs. Strangely, when I run the following code in my HTML game: window.innerWidth window.innerHeight The width appears as 533 and the height as 295. I recently discovered ...

Enhance the Material UI StepIcon by embedding real icons within the background circle

I have scoured through stack overflow but haven't come across a specific question addressing my issue. I am working on styling a Material UI Stepper component in a unique way. Most examples I've found use withStyles or makeStyles for color custom ...

Implementing sound playback within an AJAX response

Recently, I implemented a jQuery code to automatically refresh a specific div. This auto-refresh feature uses AJAX to generate notifications whenever there is a new request from a client, similar to social network notifications. I even incorporated music f ...