DEV Community

Cover image for DJ.MODE: How to Create Your First Music Player Desktop App from Scratch Using Electron JS
Natasha Arroyo for UP Mindanao SPARCS

Posted on

DJ.MODE: How to Create Your First Music Player Desktop App from Scratch Using Electron JS

This article was co-authored by @dayani_yani

Table of Contents

I.Introduction
II.Features of DJ.MODE
III.The Core Architecture: Main vs. Renderer
IV.Setting Up Your Project
V.Crafting the UI (The Renderer)
VI.Designing UI with CSS
VII.Adding Logic (The Renderer)
VIII.Launching Your App

I.Introduction

Building desktop applications used to mean learning and mastering complex, platform specific languages like C++ or Java. However, with Electron JS, you can build cross-platform desktop apps using the web tools you already know - HTML, CSS, and JavaScript. In this tutorial, we will build DJ.MODE, a retro styled music player that allows you to load and play local audio files directly from your desktop.

DJ.MODE

II.Features of DJ.MODE

  • Local Audio Playback: Play MP3 and other audio files seamlessly from your local storage.
  • Custom Playlist Support: Load multiple tracks at once and navigate through them.
  • Dynamic UI: A pixel art inspired interface with a functional progress bar and playback controls.

III.The Core Architecture: Main vs. Renderer

Before diving into the code, it is important to understand the “brain” and the “face” of an Electron app:

  • Main Process: The “brain” that interacts with your operating system.
  • Renderer Process: The "face" of your app - the HTML and CSS that the user sees. Electron combines Chromium (the engine behind Chrome) and Node.js to make this possible.

Main vs. Renderer

IV.Setting Up Your Project

To use Electron, you first need Node.js (LTS version) installed from nodejs.org. Node.js includes npm, the tool that will download Electron for you.
Windows & macOS

  1. Visit nodejs.org.
  2. Download the LTS (Long Term Support) version.
  3. Run the installer and click "Next" until finished. Once installed, open your terminal and run the following commands to initialize your project:

Bash

mkdir DJ.MODE
cd DJ.MODE
npm init -y
npm install electron --save-dev
Enter fullscreen mode Exit fullscreen mode

Update your package.json file to include the start script:
JSON

"scripts": {
  "start": "electron ."
}

Enter fullscreen mode Exit fullscreen mode

Create a main.js file. This script tells Electron how to configure and display your application window.

JavaScript

const { app, BrowserWindow } = require('electron');
function createWindow() {
  const win = new BrowserWindow({//this will create a new window for our app
    width: 480,
    height: 300,
    frame: false,  //this removes the default window frame      
    resizable: false,    
    webPreferences: {//these will allow us to use Node.js features in our script.js
      nodeIntegration: true,
      contextIsolation: false
    }
  });
  win.setMenu(null);   //this removes the default menu bar
  win.loadFile('index.html'); //this will load our index.html
}
app.whenReady().then(createWindow);
Enter fullscreen mode Exit fullscreen mode

V.Crafting the UI (The Renderer)

Create a index.html file and link the Press Start 2P font from Google Fonts for a pixelated look.
HTML

<!DOCTYPE html>
<html>
<head>
    <title>DJ>MODE</title>
    <!--this is a google font link for a pixelated font-->
    <link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="style.css"><!--this links to our style.css-->
</head>
<body>
    <div id="close-btn-container">
        <button id="close-btn">X</button><!--this button will be used to close the application-->
    </div>
    <div class="main-player-wrapper">
        <h1 id="app-title">DJ.MODE</h1>
        <h3 id="song-title">NO TRACK LOADED</h3><!--default text-->
        <div class="progress-container" id="progress-container">
            <div class="progress-bar" id="progress-bar"></div><!--this will be updated by our script.js-->
        </div>
        <div class="control-group">
            <button id="prev-btn" class="ctrl-btn">PREV</button>
            <button id="play-pause-btn" class="ctrl-btn main-btn">PLAY</button>
            <button id="next-btn" class="ctrl-btn">NEXT</button> <!--these buttons will be handled by our script.js-->
        </div>
        <button id="upload">LOAD PLAYLIST</button> <!--this button will open a file dialog to load a playlist-->
    </div>
<audio id="audio-player"></audio> <!--this will be our audio player-->
    <script src="script.js"></script> <!--this links to our script.js that will handle the logic of our player-->
</body>
</html>

Enter fullscreen mode Exit fullscreen mode

VI.Designing UI with CSS

Create a style.css file.To make the app draggable without a standard frame, use -webkit-app-region: drag.
CSS

body {
    font-family: 'Press Start 2P', cursive;
    background-color: #011638;
    color: #EFF0F2;
    user-select: none;/*prevents text selection while dragging*/
    border-radius: 3px;
    overflow: hidden;
    border: 3px solid #EEC643;
/* this centers the content vertically and horizontally */
    padding: 0;
    margin: 0;
    text-align: center;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;    
    height: 100vh;
    box-sizing: border-box;         
    -webkit-app-region: drag; /*allows you to drag the window by clicking anywhere*/  
}
.main-player-wrapper {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    width: 100%;
    padding-top: 20px;
}
#close-btn-container {   /*this keeps the close button on the top right corner*/
    position: absolute;
    top: 15px;
    right: 20px;
    -webkit-app-region: drag;
    z-index: 10;
}
#close-btn {
    background:none;
    color: #EEC643;
    border:none;
    cursor:pointer;/*this makes the cursor a pointer when hovering over the buttons*/
    font-weight: bold;
    -webkit-app-region: no-drag;
}
#close-btn:hover {
    color: #ffe347;
}
.progress-container {
    background: #13284b;
    border-radius: 5px;
    cursor: pointer;
    margin: 10px 0;
    height: 6px;
    width: 70%;
    -webkit-app-region: no-drag;
}
.progress-bar {
    background: linear-gradient(to right, #EEC643,#ecd893);
    border-radius: 5px;
    height: 100%;
    width: 0%; /*this will be updated dynamically with our script.js*/
    box-shadow: 0 0 10px #0D21A1;
}
.control-group {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 20px;/*this adds space between the buttons*/
    margin: 10px 0;
    -webkit-app-region: no-drag;
}
.ctrl-btn {
    background: #EEC643;
    border: 1px solid #0D21A1;
    color: #011638;
    border-radius: 50%;
    width: 40px;
    height: 40px;
    font-size: 8px;
    cursor: pointer;
    transition: all 0.2s ease;
    display: flex;
    justify-content: center;
    align-items: center;
    -webkit-app-region: no-drag;
}
.main-btn {
    width: 55px;
    height: 55px;
    font-size: 10px;
    background: #EEC643;
    box-shadow: 0 0 15px #011638;
}
.ctrl-btn:hover {
    transform: scale(1.1);/*this makes the buttons grow when hovered*/
    box-shadow: 0 0 20px #0D21A1;
    background: #ffe347;
}
#upload {
    background: transparent;
    border: 1px dashed #EEC643;
    border-radius: 5px;
    padding: 5px 15px;
    font-size: 10px;
    color: #d6af2e;
    cursor: pointer;
    -webkit-app-region: no-drag;
    margin-top: 5px;
}
#app-title {
    font-size: 25px;
    letter-spacing: 2px;
    color:#EEC643;
    margin: 5px 0;
}
#song-title {
    font-size: 15px;
    margin-bottom: 10px;
    max-width: 80%;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;/*this will add "..." if the title is too long*/
    line-height: 1.5;
}
audio { display: none; }/*this hides the default audio controls
                        since we are making our own custom controls*/
Enter fullscreen mode Exit fullscreen mode

VII.Adding Logic (The Renderer)

In script.js, we handle the audio logic. A key feature is using URL.createObjectURL(file) to create a temporary link to your local music files so the browser engine can play them.
JavaScript

const { ipcRenderer } = require('electron');
//these references to the HTML elements in our index.html
const audio = document.getElementById('audio-player');
const playBtn = document.getElementById('play-pause-btn');
const songTitle = document.getElementById('song-title');
const progressBar = document.getElementById('progress-bar');
const progressContainer = document.getElementById('progress-container');
const uploadBtn = document.getElementById('upload');
const closeBtn = document.getElementById('close-btn');
let playlist = [];//this will store the list of uploaded songs
let currentTrackIndex = 0;//this is to keep track of the current song playing
uploadBtn.addEventListener('click', () => {//this will trigger the file input dialog when the upload button is clicked
    const input = document.createElement('input');
    input.type = 'file';
    input.accept = 'audio/*';
    input.multiple = true;
    input.onchange = (e) => {
        playlist = Array.from(e.target.files);//this will store the selected files in the playlist array
        if (playlist.length > 0) {
            currentTrackIndex = 0;
            loadAndPlay(currentTrackIndex);//this will load and play the first selected song
        }
    };
    input.click();//this will open the file dialog
});
function loadAndPlay(index) {//this function will load and play the song at the given index in the playlist
    if (playlist.length > 0) {
        const file = playlist[index];
        const url = URL.createObjectURL(file);/*this will create a temporary URL for the selected file
        so that we can play it in the audio element*/
        audio.src = url;//this will set the audio source to the selected file
        songTitle.innerText = file.name.toUpperCase();
        audio.play();//this will start playing the song
        playBtn.innerText = "PAUSE";//this will show "PAUSE"on the button when the song is playing
    }
}
playBtn.addEventListener('click', () => {
    if (!audio.src) return; //this will not do anything if no song is loaded 
    if (audio.paused) {
        audio.play();
        playBtn.innerText = "PAUSE";//this will show "PAUSE" on the button when the song is playing
    } else {
        audio.pause();
        playBtn.innerText = "PLAY";//while this will show "PLAY" on the button when the song is paused
    }
});
document.getElementById('next-btn').addEventListener('click', () => {
    if (playlist.length > 0) {
        currentTrackIndex = (currentTrackIndex + 1) % playlist.length;//this will move to the next song in the playlist, and loop back to the first song if we are at the end
        loadAndPlay(currentTrackIndex);
    }
});
document.getElementById('prev-btn').addEventListener('click', () => {
    if (playlist.length > 0) {
        currentTrackIndex = (currentTrackIndex - 1 + playlist.length) % playlist.length;//this will move to the previous song in the playlist, and loop to the last song if we are at the beginning
        loadAndPlay(currentTrackIndex);
    }
});
audio.addEventListener('ended', () => {//this will automatically play the next song when the current song ends
    if (playlist.length > 0) {
        currentTrackIndex = (currentTrackIndex + 1) % playlist.length;
        loadAndPlay(currentTrackIndex);
    }
});
audio.addEventListener('timeupdate', () => {//this will update the progress bar as the song plays
    if (audio.duration) {
        const progressPercent = (audio.currentTime / audio.duration) * 100;//this will calculate the percentage of the song that has played
        progressBar.style.width = `${progressPercent}%`;//this will set the width of the progress bar based on the percentage of the song that has played
    }
});
progressContainer.addEventListener('click', (e) => {//this will allow the user to seek to a different part of the song by clicking on the progress bar
    if (!audio.src) return;
    const width = progressContainer.clientWidth;//this will get the width of the progress container
    const clickX = e.offsetX;//this will get the X coordinate of the click relative to the progress container
    const duration = audio.duration;
    audio.currentTime = (clickX / width) * duration;//this will set the current time of the audio based on where the user clicked on the progress bar
});
closeBtn.addEventListener('click', () => {
    window.close(); //this will close the application when the close button is clicked
});
Enter fullscreen mode Exit fullscreen mode

VII.Launching Your App

To see your creation in action, go back to your terminal and run
Bash

npm start
Enter fullscreen mode Exit fullscreen mode

You should see a window pop up! Click the "Load Playlist" button, pick one or multiple MP3 from your computer, and enjoy your hand-built music player.

Conclusion:
You have just built a functional desktop app using the same tools used to create or build websites. However this is just the beginning of your project, you can continue to expand your desktop app by adding more advanced features and other customized functions to enhance user experience.

Check out these Tutorials:
The BEST way to Create Desktop Apps (Electron)
how i code music player app🎶
GitHub Repository:
https://github.com/a-xine/DJ.MODE/tree/main

Top comments (0)