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

Tips for securing data on an aspx page

Forgive me for this seemingly silly question, but, Recently, my client requested encryption for certain information from their payment system in order to prevent users from stealing personal data. The system is web-based and developed using ASP.NET. We&a ...

Struggling with connecting relative hyperlinks

When constructing a website, I have opted to use relative hyperlinks. The file structure is organized as follows: /root/website1/ All my code resides within the website1 folder. Within website1, the structure looks like this: /css style.css /static ...

Customizing the default image of a Select dropdown field in Sencha Touch

Does anyone know how to change the default image of a select dropdown in Sencha Touch to a customized image? I've attached a screenshot for reference but can't seem to find any properties or classes to adjust this. Any guidance would be greatly a ...

Guide on transferring control from a successful jQuery event to an HTML form

I am currently using the following jQuery code to validate user details. $.ajax({ type: "POST", url: "Login", data:'uname='+encodeURIComponent(uname)+'&'+'pass='+encodeURIComponent(pass), ...

CSS Animation Effect on Transparent PNG Image

Today, while working on my website, an interesting CSS effect came to mind. I vividly remember seeing it a few months ago, but unfortunately, I couldn't find it among my bookmarks. The website featured a captivating design with a prominent logo that ...

The Wordpress admin-ajax.php script is failing to process the function and returning a "0" error code

I have been experimenting with processing AJAX requests in Wordpress and I'm following a particular tutorial to achieve this. The goal is to create a basic AJAX request that will display the post ID on the page when a link is clicked. The Approach ...

Leveraging multiple ">" CSS selectors

Here is the HTML code to style: <table id="my-table"> <tr> <td> I would like to customize this </td> <td> <table> <tr> <td> Not looking to customize t ...

Executing a function without using the eval() function

I am currently working on a JavaScript code that relies heavily on the eval function. eval(myString) The value of myString is equal to myFunc(arg), and I would like to find a way to call myFunc directly instead of using eval. Unfortunately, I have no co ...

Seamless Navigation with Bootstrap Navbar and SmoothScroll

Currently, I have a custom-built navbar that functions perfectly, with full mobile responsiveness. However, I am facing an issue with the nav-item's (headings). The nav-item's direct users to different sections of the same page using #. I have i ...

What is the method for adding a before pseudo-element to my button element?

HTML & CSS Issue <button>Get Postion</button> Below is the CSS code for styling a button: button { padding: 1rem; background-color: red; color: wheat; border: 4px solid yellowgreen; position: relative; cursor: pointer; ...

The presence of a Bootstrap addon is resulting in horizontal scrolling on mobile devices within the webpage

I am encountering a peculiar issue with an input-group in my cshtml file which features a Bootstrap addon. The problem arises on mobile devices, where upon focusing on the input field, the page scrolls horizontally to the right, revealing the right margin ...

React Router is not compatible with ReactJS app version 18

After using the command npx create-react-app, I've just set up a new ReactJS app which I think is running on React version 18 (feel free to correct me if I'm mistaken). Now, as I attempt to implement a router for this app, I find myself hesitati ...

What is the process of sending emails in PHP?

Seeking guidance on how to send mail through PHP with attached objects. As a newcomer, I would appreciate any assistance in this matter. Is there a specific server that needs to be installed? The email should be able to be sent from any email account. Ca ...

Implementing Jquery after async ajax refresh in an asp.net application

My ASP.net page is heavily reliant on jQuery. Within this page, I have a GridView placed inside an UpdatePanel to allow for asynchronous updates. <asp:GridView ID="gvMail" runat="server" GridLines="None" AutoGenerateColumns="false" ...

Ways to make the Select component in Material-UI lose its focus state once an item has been selected

Anticipated outcome: Upon selecting an item, the Menu list will promptly close, and the Select component will no longer display a focus state. The borderBottom will change to 1px solid, and the backgroundColor will turn to white. Current situation: Sele ...

The lower division remains static when the browser window is resized

Why does the div with class .lower not move to the bottom of the page when the screen is resized? Could this be because of its CSS property position:absolute;? Check out the code snippet here. #lower { width: 100%; position: absolute; bot ...

Configuring a JavaScript calendar with custom margins

Having trouble selecting a date with the Bootstrap datepicker class. After running the snippet, the calendar appears below the textbox: <input type="text" class="form-control datepicker" name="due_date" id="due_date" onclick="calendar_up()" value="" pl ...

Creating a CSS layout with three columns where the first column's width is unspecified and the second

I am looking to create a three column layout with specific width requirements. The first column should adjust based on content, the second column should fill the space between the first and third columns, and the third column should have a fixed width. Add ...

The setCountry function fails to properly change the country value

My goal is to establish a default country selection in checkbox options, I have three choices: United States, United Kingdom, and Rest of the world; Here's the constant called AVAILABLE_COUNTRIES which contains the iso codes for the mentioned countrie ...

Is it possible to test a Node CLI tool that is able to read from standard input with

I'm looking for a way to test and verify the different behaviors of stdin.isTTY in my Node CLI tool implementation. In my Node CLI tool, data can be passed either through the terminal or as command line arguments: cli.js #!/usr/bin/env node const ...