Building a Real-Time Music Player with WebSocket in Node.js
Published: 2023-04-21 06:00:57

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:

```bash
mkdir real-time-music-player
cd real-time-music-player
npm init -y
```
Next, install the following dependencies:

```bash
npm install express socket.io multer
```
`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:

```javascript
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');
});
```
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:

```html
<!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>
```
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:

```css
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;
}
```
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:

```html
<button id="select-file-button">Select File</button>
```
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:

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

selectFileButton.addEventListener('click', () => {
  fileInput.click();
});
```
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:

```javascript
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';
  }
});
```
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:

```css
#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;
}
```
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
```
This will install the multer package in your project.

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

```javascript
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');
});
```
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:

```javascript
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);
  });
});
```
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:

```css
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;
}
```
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:

```html
<input type="file" id="file-input">
```
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:

```javascript
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();
});
```
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:

```js
fileInput.addEventListener('change', () => {
  playButton.disabled = fileInput.files.length === 0;
});
```
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

{% embed https://replit.com/@SH20RAJ/SyncPlay %}