In this article I will show you how to build a music player native desktop application using Angular and Electron. This player will be compatible with Windows, Linux, and MacOS.
Electron is an open source library developed by GitHub for building cross-platform desktop applications with HTML, CSS, and JavaScript. Electron accomplishes this by combining Chromium and Node.js into a single runtime and apps can be packaged for Mac, Windows, and Linux.
First of all we will generate new Angular project using Angular CLI:
ng new music-player
After project is created successfully, cd
into the directory and install Electron with the following npm commad:
npm install --save-dev electron@latest
In root of your project, create file main.js
:
touch main.js
Let’s put some code in it. The code below will simply create a GUI window and load the index.html
file that should be available under the dist
folder after we build our Angular application.
const {app, BrowserWindow} = require('electron') const url = require("url"); const path = require("path"); let mainWindow; function createWindow() { mainWindow = new BrowserWindow({ width: 640, height: 640, webPreferences: { nodeIntegration: true } }); mainWindow.loadURL( url.format({ pathname: path.join(__dirname, `/dist/index.html`), protocol: "file:", slashes: true }) ); // Open the DevTools. // mainWindow.webContents.openDevTools(); mainWindow.on('closed', function () { mainWindow = null }) } app.on('ready', createWindow) app.on('window-all-closed', function () { if (process.platform !== 'darwin') app.quit() }); app.on('activate', function () { if (mainWindow === null) createWindow() });
Now add the main
key to set the main.js
file as the entry point in package.json
.
Also add "start:electron"
script to scripts:
"name": "music-player",
"version": "0.0.0",
"main": "main.js",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e",
"start:electron": "ng build --base-href ./ && electron ."
},
Now open angular.json
and make sure that outputPath
has the value dist
:
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist",
We will use Bootstrap for UI. So, let’s install it and its dependencies:
nenad@hp:~/playground/music-player$ npm install --save bootstrap jquery popper
Openindex.html
, and load the libraries (this can also be done trough angular.json):
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>MusicPlayer</title> <base href="/"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" type="image/x-icon" href="favicon.ico"> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"/> <script>let $ = require('jquery');</script> <script>require('popper.js');</script> <script>require('bootstrap');</script> </head> <body> <app-root></app-root> </body> </html>
I will add a few mp3songs in the assets folder.
./music-player/src/assets$ tree
.
├── Evanescence-Bring Me To Life(with lyrics).mp3
├── logo.png
├── Numb (Official Video) - Linkin Park.mp3
└── System Of A Down - Toxicity (Official Video).mp3
Create file model.ts
and export song interface:
export interface ISong {
id: number;
title: string;
path: string;
}
Now add logic to app.component.ts
:
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; import { BehaviorSubject, Subject } from 'rxjs'; import { ISong } from './model'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { currentProgress$ = new BehaviorSubject(0); currentTime$ = new Subject(); @ViewChild('player', {static: true}) player: ElementRef; songs: ISong[] = []; audio = new Audio(); isPlaying = false; activeSong; durationTime: string; ngOnInit() { this.songs = this.getListOfSongs(); this.player.nativeElement.src = this.songs[0]; this.player.nativeElement.load(); this.activeSong = this.songs[0]; this.isPlaying = false; } playSong(song): void { this.durationTime = undefined; this.audio.pause(); this.player.nativeElement.src = song.path; this.player.nativeElement.load(); this.player.nativeElement.play(); this.activeSong = song; this.isPlaying = true; } onTimeUpdate() { // Set song duration time if (!this.durationTime) { this.setSongDuration(); } // Emit converted audio currenttime in user friendly ex. 01:15 const currentMinutes = this.generateMinutes(this.player.nativeElement.currentTime); const currentSeconds = this.generateSeconds(this.player.nativeElement.currentTime); this.currentTime$.next(this.generateTimeToDisplay(currentMinutes, currentSeconds)); // Emit amount of song played percents const percents = this.generatePercentage(this.player.nativeElement.currentTime, this.player.nativeElement.duration); if (!isNaN(percents)) { this.currentProgress$.next(percents); } } // Play song that comes after active song playNextSong(): void { const nextSongIndex = this.songs.findIndex((song) => song.id === this.activeSong.id + 1); if (nextSongIndex === -1) { this.playSong(this.songs[0]); } else { this.playSong(this.songs[nextSongIndex]); } } // Play song that comes before active song playPreviousSong(): void { const prevSongIndex = this.songs.findIndex((song) => song.id === this.activeSong.id - 1); if (prevSongIndex === -1) { this.playSong(this.songs[this.songs.length - 1]); } else { this.playSong(this.songs[prevSongIndex]); } } // Calculate song duration and set it to user friendly format, ex. 01:15 setSongDuration(): void { const durationInMinutes = this.generateMinutes(this.player.nativeElement.duration); const durationInSeconds = this.generateSeconds(this.player.nativeElement.duration); if (!isNaN(this.player.nativeElement.duration)) { this.durationTime = this.generateTimeToDisplay(durationInMinutes, durationInSeconds); } } // Generate minutes from audio time generateMinutes(currentTime: number): number { return Math.floor(currentTime / 60); } // Generate seconds from audio time generateSeconds(currentTime: number): number | string { const secsFormula = Math.floor(currentTime % 60); return secsFormula < 10 ? '0' + String(secsFormula) : secsFormula; } generateTimeToDisplay(currentMinutes, currentSeconds): string { return `${currentMinutes}:${currentSeconds}`; } // Generate percentage of current song generatePercentage(currentTime: number, duration: number): number { return Math.round((currentTime / duration) * 100); } onPause(): void { this.isPlaying = false; this.currentProgress$.next(0); this.currentTime$.next('0:00'); this.durationTime = undefined; } getListOfSongs(): ISong[] { return [ { id: 1, title: 'Evanescence-Bring Me To Life(with lyrics).mp3', path: './assets/Evanescence-Bring Me To Life(with lyrics).mp3' }, { id: 2, title: 'Numb (Official Video) - Linkin Park.mp3', path: './assets/Numb (Official Video) - Linkin Park.mp3' }, { id: 3, title: 'System Of A Down - Toxicity (Official Video).mp3', path: './assets/System Of A Down - Toxicity (Official Video).mp3' } ]; } }
Also add code to app.component.html
:
<div class="ui container center-screen">
<div class="row justify-content-center">
<div class="col-12">
<img src="'../../assets/logo.png" alt="..." class="img-thumbnail">
</div>
</div>
<br>
<br>
<div class="row">
<div class="col-12"><h4>{{ activeSong?.title }}</h4></div>
</div>
<br>
<div class="row justify-content-center">
<div class="col-12">
<div class="container">
<div class="row">
<div class="col-6 text-left">
{{ currentTime$ | async }}
</div>
<div class="col-6 text-right" *ngIf="player?.duration">
{{ durationTime }}
</div>
</div>
</div>
<div class="progress">
<div class="progress-bar bg-info" role="progressbar"
[ngStyle]="{'width': (currentProgress$ | async) + '%'}"
[attr.aria-valuenow]="(currentProgress$ | async) "
aria-valuemin="0" aria-valuemax="100">
{{ '<<' }}
<button type="button" class="btn btn-outline-dark" *ngIf="isPlaying" (click)="player.pause()">Stop</button> <button type="button" class="btn btn-outline-dark" *ngIf="!isPlaying" (click)="playSong(activeSong)">Play</button> <button type="button" class="btn btn-outline-dark" (click)="playNextSong()"><b>{{ '>>' }}</b></button> </div>
Now let’s run command to start electron
npm run start:electron
Result:
You now have a native desktop music player! The remaining step is to simply build it for the platforms you want to support (Windows, Mac, Linux).
If someone wants to play with this or add some features, github link :
https://github.com/nenadb97/angular-electron-music-player
Thanks for reading 🙂