Building a Real-Time Music Player with WebSocket in Node.js

Sh Raj - Apr 21 '23 - - Dev Community

If you want to build a real-time music player that syncs playback across multiple devices, using WebSockets in Node.js is a great way to do it. In this tutorial, we will show you how to build a music player that lets you select a song from your device and play it on multiple devices in sync.

Prerequisites

Before we begin, make sure you have the following:

Node.js installed on your machine
Basic knowledge of JavaScript and Node.js

Project Setup

We will start by creating a new Node.js project and installing the necessary dependencies. Open up a terminal and run the following commands:

mkdir real-time-music-player
cd real-time-music-player
npm init -y
Enter fullscreen mode Exit fullscreen mode

Next, install the following dependencies:

npm install express socket.io multer
Enter fullscreen mode Exit fullscreen mode

express: a popular Node.js web framework for building web applications
socket.io: a JavaScript library for building real-time applications with WebSockets
multer: a middleware for handling multipart/form-data, which is used for uploading files
Create a new file named index.js in the root of your project and paste in the following code:

const express = require('express');
const http = require('http');
const socketio = require('socket.io');
const multer = require('multer');
const path = require('path');

const app = express();
const server = http.createServer(app);
const io = socketio(server);

const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, './uploads');
  },
  filename: function (req, file, cb) {
    const ext = path.extname(file.originalname);
    const name = file.originalname.replace(ext, '');
    cb(null, `${name}-${Date.now()}${ext}`);
  }
});

const upload = multer({
  storage: storage,
  fileFilter: function (req, file, cb) {
    if (['audio/mpeg', 'audio/mp3'].includes(file.mimetype)) {
      cb(null, true);
    } else {
      cb(new Error('Invalid file type.'));
    }
  }
});

app.use(express.static('public'));

app.post('/upload', upload.single('file'), (req, res) => {
  const file = req.file;
  if (!file) {
    res.status(400).json({ message: 'No file uploaded.' });
  } else {
    res.json({ message: 'File uploaded successfully.' });
    io.emit('file-uploaded', file.filename);
  }
});

server.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});
Enter fullscreen mode Exit fullscreen mode

This code sets up an Express server and Socket.IO to handle real-time communication between clients. It also uses Multer to handle file uploads.

Next, create a new folder named public in the root of your project. Inside this folder, create an HTML file named index.html with the following code:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Real-Time Music Player</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      background-color: #f7f7f7;
      margin: 0;
      padding: 0;
    }
    .container {
      max-width: 800px;
      margin: 0 auto;
      padding: 20px;
      display: flex;
      flex-direction: column;
      align-items: center;
    }
    h1 {
      margin-top: 0;
    }
    #select-file {
      margin-bottom: 20px;
    }
    #play-button {
      background-color: #4CAF50;
      color: white;
      border: none;
      padding: 10px 20px;
      text-align: center;
      text-decoration: none;
      display: inline-block;
      font-size: 16px;
      margin-bottom: 20px;
      cursor: pointer;
    }
    #play-button:disabled {
      background-color: #a0a0a0;
      cursor: default;
    }
  </style>
</head>
<body>
  <div class="container">
    <h1>Real-Time Music Player</h1>
    <input type="file" id="select-file">
    <button id="play-button" disabled>Play</button>
    <audio id="audio-player"></audio>
  </div>
  <script src="/socket.io/socket.io.js"></script>
  <script>
    const socket = io();
    const fileInput = document.getElementById('select-file');
    const playButton = document.getElementById('play-button');
    const audioPlayer = document.getElementById('audio-player');

    fileInput.addEventListener('change', () => {
      if (fileInput.files.length > 0) {
        playButton.disabled = false;
      } else {
        playButton.disabled = true;
      }
    });

    playButton.addEventListener('click', () => {
      const file = fileInput.files[0];
      const formData = new FormData();
      formData.append('file', file);

      fetch('/upload', {
        method: 'POST',
        body: formData
      })
      .then(response => response.json())
      .then(data => {
        audioPlayer.src = `/uploads/${data.message}`;
        audioPlayer.play();
      });

      socket.on('file-uploaded', filename => {
        audioPlayer.src = `/uploads/${filename}`;
        audioPlayer.play();
      });
    });
  </script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

This HTML file contains an input field for selecting a file, a button to play the selected file, and an audio player element for playing the file. It also includes JavaScript code to handle file uploads and real-time communication with Socket.IO.

CSS Styling

To make our music player look good, we will add some CSS styling. Create a new file named style.css in the public folder and paste in the following code:

body {
  font-family: Arial, sans-serif;
  background-color: #f7f7f7;
  margin: 0;
  padding: 0;
}
.container {
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
  display: flex;
  flex-direction: column;
  align-items: center;
}
h1 {
  margin-top: 0;
}
#select-file {
  margin-bottom: 20px;
}
#play-button {
  background-color: #4CAF50;
  color: white;
  border: none;
  padding: 10px 20px;
  text-align: center;
  text-decoration: none;
  display: inline-block;
  font-size: 16px;
  margin-bottom: 20px;
  cursor: pointer;
}
#play-button:disabled {
  background-color: #a0a0a0;
  cursor: default;
}
Enter fullscreen mode Exit fullscreen mode

This CSS code styles the different elements of our music player, including the file input field, play button, and audio player. We use the max-width property to limit the width of the container to 800 pixels, and the margin and padding properties to center and add spacing to the content. The flex properties are used to vertically center the content and make it responsive on different screen sizes. The #play-button styles set the background color to green and change the cursor to a pointer when hovering over it. We also add a :disabled pseudo-class to the button, which sets the background color to gray and disables the cursor when the button is disabled.

Selecting a Song from Device

To give the user the option to select a song from their device, we will add a new button element that will trigger a file picker dialog when clicked. We will then modify the existing code to use the selected file.

Modify the index.html file to add a new button element below the existing #play-button element:

<button id="select-file-button">Select File</button>
Enter fullscreen mode Exit fullscreen mode

This creates a new button with the ID select-file-button. We will use this button to trigger the file picker dialog. Now, modify the JavaScript code to handle the click event on the new button and get the selected file:

const selectFileButton = document.getElementById('select-file-button');

selectFileButton.addEventListener('click', () => {
  fileInput.click();
});
Enter fullscreen mode Exit fullscreen mode

This code gets the #select-file input element and adds an event listener to the #select-file-button element that simulates a click on the input element when the button is clicked. This will open the file picker dialog, allowing the user to select a file.

Next, we need to modify the existing change event listener on the #select-file input element to update the display of the selected file:

fileInput.addEventListener('change', () => {
  if (fileInput.files.length > 0) {
    playButton.disabled = false;
    selectFileButton.textContent = fileInput.files[0].name;
  } else {
    playButton.disabled = true;
    selectFileButton.textContent = 'Select File';
  }
});
Enter fullscreen mode Exit fullscreen mode

This code checks if the input element has a selected file and updates the text content of the #select-file-button element to display the file name. If no file is selected, it sets the text content back to "Select File".

Adding CSS for File Selection

To style the new button, we will modify the style.css file. Add the following CSS code to the bottom of the file:

#select-file-button {
  background-color: #2196F3;
  color: white;
  border: none;
  padding: 10px 20px;
  text-align: center;
  text-decoration: none;
  display: inline-block;
  font-size: 16px;
  margin-bottom: 20px;
  cursor: pointer;
}
Enter fullscreen mode Exit fullscreen mode

This code styles the #select-file-button element with a blue background color, white text, and a pointer cursor on hover.

Deploying on Replit

Now that we have our app working locally, we can deploy it to Replit so that it can be accessed by others.

Create a new Replit project by clicking the "+" button on the top right of the Replit dashboard and selecting "HTML, CSS, JS" as the language.
Copy the contents of your local project's index.html, style.css, and server.js files into the corresponding files in the Replit project.
In the Replit project, click the "Run" button to start the server. The app should now be accessible through the Replit web URL.

However, to allow other users to access the app and play songs from their own devices, we need to make a few modifications to the server code to handle file uploads.

First, we need to install the multer package, which is a middleware for handling multipart/form-data, which is used for file uploads. Open the Replit console by clicking on the "Shell" button in the bottom left corner of the Replit window, and run the following command:

npm install multer
Enter fullscreen mode Exit fullscreen mode

This will install the multer package in your project.

Next, modify the server.js file to include the multer middleware and handle file uploads:

const express = require('express');
const multer = require('multer');
const app = express();
const upload = multer({ dest: 'uploads/' });

app.use(express.static('public'));

app.post('/upload', upload.single('song'), (req, res) => {
  console.log(req.file);
  res.send('File uploaded successfully!');
});

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});
Enter fullscreen mode Exit fullscreen mode

This code creates a new upload object using multer and sets the destination folder to uploads/. We then modify the /upload route to use the upload.single middleware, which handles a single file upload with the field name song. When a file is uploaded, the server logs the file information and sends a response with the text "File uploaded successfully!".

Now, we need to modify the JavaScript code to send the selected file to the server using an AJAX request when the #play-button is clicked:

playButton.addEventListener('click', () => {
  const formData = new FormData();
  formData.append('song', fileInput.files[0]);

  fetch('/upload', {
    method: 'POST',
    body: formData
  })
  .then(response => {
    console.log(response);
  })
  .catch(error => {
    console.error(error);
  });
});
Enter fullscreen mode Exit fullscreen mode

This code creates a new FormData object and appends the selected file to it. It then sends an AJAX POST request to the /upload route with the form data. When the server responds, it logs the response to the console.

Adding CSS for Audio Player

To make the audio player more visually appealing, we will modify the style.css file to add some CSS styles. Add the following CSS code to the bottom of the file:

audio {
  width: 100%;
  margin-top: 20px;
}

#player-container {
  margin-top: 20px;
  max-width: 800px;
  display: flex;
  flex-direction: column;
  align-items: center;
}

#song-title {
  font-size: 24px;
  margin-top: 20px;
}

#player-controls {
  margin-top: 20px;
  display: flex;
  justify-content: space-between;
  width: 100%;
}

#player-controls button {
  background-color: #2196F3;
  color: white;
  border: none;
  padding: 10px 20px;
  text-align: center;
  text-decoration: none;
  display: inline-block;
  font-size: 16px;
  margin: 0 10px;
  cursor: pointer;
}

#player-controls button:disabled {
  background-color: gray;
  cursor: not-allowed;
}
Enter fullscreen mode Exit fullscreen mode

This code styles the audio element to be 100% width and adds some margin to separate it from the other content. It also adds styles to the #player-container, #song-title, and #player-controls elements to center them and add some margin. The styles for the buttons add a blue background color, white text, and some padding to make them easier to click.

Selecting Songs from Device

To allow users to select a song from their own device instead of using the default song, we will modify the HTML and JavaScript code to add a file input element.

First, modify the index.html file to add a new input element:

<input type="file" id="file-input">
Enter fullscreen mode Exit fullscreen mode

This adds a file input element with the ID file-input to the HTML page.

Next, modify the JavaScript code to use the selected file when the #play-button is clicked:

const fileInput = document.getElementById('file-input');

playButton.addEventListener('click', () => {
  let songUrl = '/song.mp3';
  if (fileInput.files.length > 0) {
    songUrl = URL.createObjectURL(fileInput.files[0]);
  }

  audioElement.src = songUrl;
  audioElement.play();
});
Enter fullscreen mode Exit fullscreen mode

This code gets the file-input element and checks if a file has been selected. If a file has been selected, it creates a URL for the selected file using the URL.createObjectURL method. If no file has been selected, it uses the default song URL.

Finally, modify the JavaScript code to enable the #play-button only if a file has been selected:

fileInput.addEventListener('change', () => {
  playButton.disabled = fileInput.files.length === 0;
});
Enter fullscreen mode Exit fullscreen mode

This code listens for the change event on the file-input element and disables the #play-button if no file has been selected.

Deploying the App on Replit
To deploy the app on Replit, click on the "Run" button in the top center of the Replit window. This will start the server and make the app accessible through the Replit web URL.

To share the app with other users, click on the "Share" button in the top right corner of the Replit window. This will generate a share link that you can send to others.

Congratulations! You have successfully created an online audio player that allows users to play songs from their own devices. By modifying the code, you can customize the app further and add more features.

Example

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .