DEV Community

Cover image for Building a Music Entertainment Application with ReactJS, NextJS, Algolia, and Firebase
OpenReplay Tech Blog
OpenReplay Tech Blog

Posted on • Originally published at blog.openreplay.com

Building a Music Entertainment Application with ReactJS, NextJS, Algolia, and Firebase

by author Tuduo Victory

A music streaming application, as the name implies, is simply an application that lets you stream music on your device. Streaming is a process by which we listen to music or watch a video in real-time rather than downloading it on our device. Some apps that offer this service include Spotify, Netflix, and Audiomack, among others. In this tutorial, we will be building a simple music application where we can upload music of our choice and stream them directly from the app. We will also learn how to use Google Firebase Cloud storage, access the Firestore database and use Algolia service to provide data from our database to our application.

Setting up our Application

For the purpose of this tutorial, we will be making use of different technologies in building our application. We will be building basically a NextJS front-end application with Firebase Cloud storage, Firestore database, and Algolia on the back-end. NextJS is an open source development framework built on NodeJS that allows React-based applications to be rendered server-side. Firebase Cloud storage is a cloud-hosted storage that lets us store files of any kind on the cloud while Firestore refers to the NoSQL real-time database on Firebase that lets you build collaborative apps by allowing secured access to your database directly from the server-side. Algolia is a hosted search engine that can provide real-time results on search from the very first keystroke entered.

Installing dependencies

Firstly, we will set up our work environment by installing our front-end framework which is NextJS. Run the following code in our Command Line Interface in the chosen directory where we want to create this application.

for npm users:

npx install create-next-app musicapp --use-npm
Enter fullscreen mode Exit fullscreen mode

for yarn users:

npx install create-next-app musicapp
Enter fullscreen mode Exit fullscreen mode

In the above bash scripts, we created a folder called musicapp which has the NextJS package installed. This will serve as our work folder.

Configuring our page

Our newly created application is set up to use the NextJS framework and has a template in it upon installation. In our index.js file, we will clear the code within our return block and replace it with the following:

import Head from 'next/head'
export default function Home() {
 return (
   <div >
     <h1>components here</h1>
   </div>
)
}
Enter fullscreen mode Exit fullscreen mode

The import head is used to add custom title or meta tag data to our web application. The head is used as illustrated below:

import Head from 'next/head'
export default function Home() {
 return (
   <div>
     <head>
       <title>Music Application</title>
       <meta name="keywords" content="music, streaming, entertainment"></meta>
     </head>
     <h1>components here</h1>
   </div>
);
}
Enter fullscreen mode Exit fullscreen mode

Here we created a head and gave our app the title "Music Application". We also defined some keywords for our metadata search words.

NextJS allows for easy routing without having to configure a 3rd-party router within our application. In ReactJS we had to install and use react-routers to handle routing between our pages in our application. NextJS already has routers set up for us. We just need to create a new page in the pages folder and we can route it to our browser with the path.
create a new file called library.js in your pages folder with the following:

import React from "react";
import Head from 'next/head'
const library = () => {
 return (
   <div>
      <head>
       <title>Library</title>
       <meta name="keywords" content="music, streaming, entertainment"></meta>
     </head>
     <h1>Music LIbrary here:</h1>
   </div>
);
};
export default library;
Enter fullscreen mode Exit fullscreen mode

This will be our page for uploading music to our cloud storage and database. We will cover building this page later on in this tutorial.
now if we run our program using the following commands in our CLI we can see the output of our application in our browser:

npm run dev
Enter fullscreen mode Exit fullscreen mode

Navigate to the URL our server is hosted on, we see the homepage as our index.js component.

landing page

Adding "/library" to our URL will redirect you to our library.js component:

music library

Back in our index.js file. we will be using the react useState hook:

import {UseState} from 'react';
Enter fullscreen mode Exit fullscreen mode

UseState is a built-in hook for React. It allows us to add states to our functional components. Using this we can create a state without switching to class components.

To show how the elements will be aligned and the music player works, we will be using dummy data supplied from our work folder. For this, create a folder called images and another called songs within our public folder. items within the public folder are directly accessible to every page of our application in NextJS.
In my image folder I have two pictures named "img1" and "img2". I also have two song files in my song folder. We will use this dummy data with the useState react hook as follows:

export default function Home() {
 const [songs, setsongs] = useState([
  {
     title: "song 1",
     artist: "artist 1",
     img_src: "./images/img1.jpg",
     src: "./songs/Måneskin - Beggin ( Testo)_2.mp3",
  },
  {
     title: "song 2",
     artist: "artist 2",
     img_src: "./images/img2.jpg",
     src: "./songs/Young Dumb & Broke Khalid .mp3",
  },
]);
 return (
   <div>
     <head>
       <title>Music Application</title>
       <meta name="keywords" content="music, streaming, entertainment"></meta>
     </head>
     <h1>components here</h1>
   </div>
);
}
Enter fullscreen mode Exit fullscreen mode

The paths to your images will be in the img_src and that to your songs will be in src.

We will then create a folder within our src folder called components. Here, we will have the different components that make up our application. we will have two major components: a play.js component and a search.js component.

In our working directory, there is a file called _app.js within the pages folder. This file lets us render pages on our app server-side. We will create a component that will wrap around all our pages. To do this, create a file called Layout.js within our component folder. we will also create a style sheet for this component called layout.module.css. Note that .module.css is the naming convention for style sheet files in NextJS. In the following code, I have created a Layout component that adds a header and a footer to our page. Since the _app.js renders children as props. You can bring that prop and use it within our layout component as shown below:

import React from "react";
import style from "../styles/layout.module.css";
const Layout = ({children}) => {
 return (
   <div className={style.container}>
     <div className={style.top}>
       <h3>Music Streaming</h3>
         </div>
        {children}
     <div className={style.footer}>
       <h3>Browse and listen to music of your choice</h3>
     </div>
   </div>
);
};
export default Layout;
Enter fullscreen mode Exit fullscreen mode

In our layout.module.css file we have the following styles:

.container {
 font-weight: bold;
 color: #333;
}
.top,
.footer {
   height: 50px;
   width: 100%;
   color:#fff;
   background: rgb(73, 13, 236);
   display: flex;
   align-items: center;
   padding-left: 15px;
}

Enter fullscreen mode Exit fullscreen mode

Then, in our _app.js file, we will import our Layout component and then wrap our component props within the layout as shown below:

import Layout from '../components/Layout'
import '../styles/globals.css'
function MyApp({ Component, pageProps }) {
 return (
   <Layout>
     <Component {...pageProps} />
   </Layout>
);
}
export default MyApp
Enter fullscreen mode Exit fullscreen mode

If we return to our browser, on our main page we will have the output below:

home after wrapping layout component

Note that if you navigate to the "/library" path in the URL you will still have the Layout component wrapping it also as it is part of the component props.

For our music player, we will create three additional components called Player.js, PlayerDetail.js, and PlayerControls.js within our component folder. We will also create a style sheet called player.module.css and import it within our Player.js. Open Player.js and populate it with the following code:

import React from 'react'
import style from "../styles/player.module.css"
function Player() {
   return (
       <div className={style.cplayer}>
           <audio></audio>
           <h4>Playing now</h4>
          {/*music search functionality */}
          {/*player Details here */}
          {/*plaer controls here */}
           <p><strong>Next up: </strong>next song here</p>
       </div>
  )
}
export default Player
Enter fullscreen mode Exit fullscreen mode

Back in our index.js we are going to set additional variables using the usestate hook that will handle the particular song being played.

const [currentSongIndex, setCurrentSongIndex] = useState(0);
const [nextSongIndex, setNextSongIndex] = useState(currentSongIndex + 1);
Enter fullscreen mode Exit fullscreen mode

We will then add imports for our Player component to our index.js file. Add the following code to do this:

import Player from "../components/Player"
Enter fullscreen mode Exit fullscreen mode

After this, we add the Player component within our Home component and return two props with it which will be the song and the next song.

import Head from "next/head";
import Player from "../components/Player"
import { useState } from "react";
export default function Home() {
 ...
 const [currentSongIndex, setCurrentSongIndex] = useState(0);
 const [nextSongIndex, setNextSongIndex] = useState(currentSongIndex + 1);
 return (
   <div>
     <head>
       <title>Music Application</title>
       <meta name="keywords" content="music, streaming, entertainment"></meta>
     </head>
     <h1>components here</h1>
     <Player
       song={songs[currentSongIndex]}
       nextSong={songs[nextSongIndex]}
     />
   </div>
);
}
Enter fullscreen mode Exit fullscreen mode

These props will be used to indicate the current song playing and the next song to be played. We can then reference these props within our Player component in Player.js.

function Player(props) {
   return (
       <div className={style.cplayer}>
           <audio></audio>
           <h4>Playing now</h4>
          {/*music search functionality */}
          {/*player Details here */}
          {/*plaer controls here */}
           <p><strong>Next up: </strong>{props.nextSong.title} by {props.nextSong.artist}</p>
       </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

If you return to your browser you will have an output similar to the image below:

components added to home page

To add player details to our app, add the following code to our PlayerDetails component earlier created:

import React from 'react'
import style from "../styles/player.module.css"
function PlayerDetails(props) {
   return (
       <div className={style.playerdetails}>
           <div className={style.detailsimg}>
               <img src={props.song.img_src} alt=""></img>
           </div>
           <h3 className={style.detailstitle}>{props.song.title}</h3>
           <h3 className={style.detailsartist}>{props.song.artist}</h3>
       </div>
  )
}
export default PlayerDetails
Enter fullscreen mode Exit fullscreen mode

We will then import this within our Player.js component

import PlayerDetails from './PlayerDetails'
Enter fullscreen mode Exit fullscreen mode

We can now add the component within our Player and pass the props of the song to it as follows:

function Player(props) {
   return (
       <div className={style.cplayer}>
           <audio></audio>
           <h4>Playing now</h4>
          {/*music search functionality */}
           <PlayerDetails song={props.song}/>
          {/*plaer controls here */}
           <p><strong>Next up: </strong>{props.nextSong.title} by {props.nextSong.artist}</p>
       </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

In our browser, you will have the image shown along with the title and artist of the song.

We will be making use of the react-audio package to play our audio. to do this press ctrl+c to terminate your server in your terminal, then install using:
for npm users:

npm i react-h5-audio-player
Enter fullscreen mode Exit fullscreen mode

for yarn users:

yarn add react-h5-audio-player
Enter fullscreen mode Exit fullscreen mode

This pkg has music player controls such as seek, volume, and others pre-built for use. We just need to import it for use within our application.

import React from "react";
import style from "../styles/player.module.css";
import AudioPlayer from "react-h5-audio-player";
import "react-h5-audio-player/lib/styles.css";
function PlayerControls(props) {
 return (
   <div className={style.playercontrols}>
     <AudioPlayer
       autoPlay
       src={props.song.src}
       onPlay={(e) => console.log("onPlay")}
       // other props here
       showSkipControls
       autoPlayAfterSrcChange

     />
   </div>
);
}
export default PlayerControls;
Enter fullscreen mode Exit fullscreen mode

After this, we import this player control component into our Player.js file and add it to our Home

function Player(props) {
   return (
       <div className={style.cplayer}>
           <audio></audio>
           <h4>Playing now</h4>
          {/*music search functionality */}
           <PlayerDetails song={props.song}/>
           <PlayerControls song={props.song}/>
           <p><strong>Next up: </strong>{props.nextSong.title} by {props.nextSong.artist}</p>
       </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

For our search functionality, we will create a new file called Search.js in our component folder. We will set some custom styles for this component to show where it will be placed in our Application. We will cover building this component later in this tutorial. In our Search.js file we have the following codes:

import React from 'react'
import style from "../styles/search.module.css"
function Search() {
   return (
       <div className={style.searchcont}>
          {/*search*/}
       </div>
  )
}
export default Search
Enter fullscreen mode Exit fullscreen mode

In our search.module.css file we have:

.searchcont{
   height:100%;
   width: 60%;
   background: #ddd;
}

Enter fullscreen mode Exit fullscreen mode

Then, we import this component into our index.js file and arrange it side by side with the player component using a new style sheet called arrangement.module.css.

import Head from "next/head";
import Player from "../components/Player"
import Search from "../components/Search"
import { useState } from "react";
import style from "../styles/arrangement.module.css"
export default function Home() {
...
 return (
   <div className={style.maincont}>
     <head>
       <title>Music Application</title>
       <meta name="keywords" content="music, streaming, entertainment"></meta>
     </head>
     <Search/>
     <Player
       song={songs[currentSongIndex]}
       nextSong={songs[nextSongIndex]}
     />
   </div>
);
}
Enter fullscreen mode Exit fullscreen mode

In the arrangement.module.css we have the following styles:

.maincont{
   display: flex;
}
Enter fullscreen mode Exit fullscreen mode

Now, we can proceed with styling our music player in player.module.css stylesheet:

.cplayer{
   margin: 0;
   box-sizing: border-box;
   font-family: monospace;
   background: #313131;
   color:#fff;
   width: 100%;
   display: flex;
   align-items: center;
   justify-content: center;
   min-height: 100vh;
   flex-direction: column;
   border-top-left-radius: 13px;
   border-bottom-left-radius: 13px;
   padding: 50px;
   padding-top: 3px;
   /* box-shadow: inset -6px -6px 12px rgba(0,0,0,.8); */
}
.cplayer h4{
   font-size: 14px;
   text-transform: uppercase;
   font-weight: 400;
   text-align: center;
}
.cplayer > p{
   color: #aaa;
   font-size: 14px;
   text-align: center;
   font-weight: 400;
}
.playerdetails .detailsimg{
   position: relative;
   width: fit-content;
   margin: 0 auto;
}
.detailsimg img{
   display: block;
   margin: 0px auto;
   width: 100%;
   max-width: 250px;
   border-radius: 50%;
   box-shadow: 6px 6px 12px rgba(0,0,0,.8), -6px -6px 12px rgba(255,255,255,0.4);
}
.detailstitle{
   color: #eee;
   font-size: 20px;
   text-shadow: 2px 2px 4px rgba(0,0,0,.8), -2px -2px 4px rgba(255,255,255,0.4);
   text-align: center;
   margin-bottom: 10px;
}
.detailsartist{
   color: #aaa;
   font-size: 20px;
   text-shadow: 2px 2px 4px rgba(0,0,0,.8), -2px -2px 4px rgba(255,255,255,0.4);
   text-align: center;
   margin-bottom: 20px;
}
Enter fullscreen mode Exit fullscreen mode

After this, we will build our search component with Algolia.

Algolia

Create a user account

To create a user account, navigate in our browser to Algolia and click on Start Free. You can create an account using the available options.

Create an index called Music

Upon creation of the account, you will be prompted to create an index. An index refers to the place where the data which would be used by the search engine is stored. This is equivalent to what "tables" are for databases. We would create an index called Music.

Customization

You can customize searchable attributes and rankings based on our configuration. The former specifies the search keywords to be used in filtering through our search and the latter defines the words that are used to order our items. For now, select sample data set media.
In the configuration, you can set searchable attributes to the attribute of your choice. Under ranking and sorting, click on “add custom ranking”, “post date” to make the search show the most recent results first. You can test how it works in the run demo option.

Next, we will create our database on Firebase which will automatically update our Algolia content when changes are made.

Firebase

Google Firebase is a Google-backed application development software that enables developers to develop apps for different platforms. Firebase provides us with a real-time database: Firestore. Firestore is a no-SQL database which lets us build high performance applications with the same features offered by traditional databases. With it we can easily store, synchronize and fetch data for our application.

Create Project

Navigate to Firebase in your browser, click on “Get Started”, sign in and create a new project. Key in the name of your project, check the other options, and create a new project called Music application.

You can now install the firebase package for use within your application. Press ctrl+c to end server in your terminal and type:

npm install --save firebase
npm install -g firebase-tools
firebase init functions
Enter fullscreen mode Exit fullscreen mode

Select Yes as the default for everything, select Use existing apps, and select the app we created. Select install dependencies at the end. Once the process is finished, in the CLI run:

cd functions
Enter fullscreen mode Exit fullscreen mode

Next, you will need to set up your app and API keys. you can find these on your dashboard in Algolia.

firebase functions:config:set algolia.app=Your_app_id algolia.key=admin_api_key
Enter fullscreen mode Exit fullscreen mode

We will need to use firebase functions to link the data in our Firestore database to our Algolia index. Note that you need to be on the blaze plan on firebase to use functions. To do this, click on the extensions in the dashboard and select Algolia. It will then require you to select the “index”, then the name of the Firestore collection. In this case, we will create a collection called Music. Leave the attribute field to be indexed blank so that all fields in the collection will be indexed. We can add the Algolia ID and API keys gotten from the Algolia dashboard. Then click on install extension to finalize.

Adding dummy data to Firebase database

To test how our Firestore database works with Algolia, we will use faker.js npm package to populate our firebase database and reflect it in Algolia.

npm install faker
Enter fullscreen mode Exit fullscreen mode

Create a new file called seed.js in the functions folder. We will use this to populate our firebase with faker details.

We will also need to add the Google Cloud Firestore API to our app and create a key on google Cloud for our application.

Download and bring the key to your project directory. Then in your Firebase project settings, under Service Accounts, copy the code and add the path to your key in the space required.

const admin = require("firebase-admin");
var serviceAccount = require("path to your key");
paste admin.intializeApp code here
const faker = require("faker");
const db = admin.firestore();
const fakeIt = () => {
 return db.collection("Music").add({
   username: faker.internet.userName(),
   email: faker.internet.email(),
   avatar: faker.internet.avatar(),
});
};
Array(5).fill(0).forEach(fakeIt);

Enter fullscreen mode Exit fullscreen mode

To run this, open the command shell window and key in:

node seed.js
Enter fullscreen mode Exit fullscreen mode

Automatically it would create a new database collection for us called "Music". If we navigate to our index on Algolia we will see that the new data created by faker on our database is shown there.

Data generated by faker

In search.js we will use React instant search package to create our search component. To install this, in your command window key in:

npm install algoliasearch react-instantsearch-dom react-instantsearch
Enter fullscreen mode Exit fullscreen mode

Then in our search.js component, set up with the following code:

import React from "react";
import style from "../styles/search.module.css";
import algoliasearch from "algoliasearch/lite";
import { InstantSearch, SearchBox, Hits } from "react-instantsearch-dom";
const searchClient = algoliasearch(
 "algolia id",
 "algolia key"
);
function Search() {
 return (
   <div className={style.searchcont}>
     <InstantSearch searchClient={searchClient} indexName="Music">
             <SearchBox translations={{placeholder: 'Search for music'}}/>
             <Hits/>
     </InstantSearch>
   </div>
);
}
export default Search;
Enter fullscreen mode Exit fullscreen mode

This will return the hits from Algolia to our search component. You can use the search bar to search through these hits based on the attributes we earlier defined. We will delete this data from firebase and proceed to create our upload page to upload music to firebase, we will then style our search component and play the chosen music on click.

Open Source Session Replay

Debugging a web application in production may be challenging and time-consuming. OpenReplay is an Open-source alternative to FullStory, LogRocket and Hotjar. It allows you to monitor and replay everything your users do and shows how your app behaves for every issue.
It’s like having your browser’s inspector open while looking over your user’s shoulder.
OpenReplay is the only open-source alternative currently available.

OpenReplay

Happy debugging, for modern frontend teams - Start monitoring your web app for free.

Uploading media Content to Firebase

To create a music upload page in Library.js, firstly we will create and import a CSS file for Library.js, then create a new file called Upload.js in the components folder and add an import for it in the Library file. In Upload.js we will create the components to upload music to our firebase. We will store the records in the Firestore database and store the files in Firebase storage. Within Library.js:

import Upload from "../components/Upload"
import style from "../styles/library.module.css"
Enter fullscreen mode Exit fullscreen mode

Here we have added imports for the Upload component and the stylesheet.

To prevent an error message that occurs when we initialize firebase multiple times we would create a file separately for firebase config and import it when we need to initialize firebase. Create a file called firebase.js in your root folder and populate it with the following code:

import firebase from "firebase";
import "firebase/firestore";
const firebaseConfig = {
//you will find this in your project settings on firebase
};
!firebase.apps.length
 ? firebase.initializeApp(firebaseConfig).firestore()
: firebase.app().firestore();
var db = firebase.firestore();
export default db
Enter fullscreen mode Exit fullscreen mode

To use firebase storage we need to first create a storage in our firebase account. You can do this by clicking on storage on the dashboard, then new. In our Upload.js:

import React from "react";
import style from "../styles/library.module.css";
import db from "../firebase"
import firebase from "firebase";
import "firebase/storage"
function Upload() {
   const changed = (e) =>{
       var file = e.target.files[0];
       var storageRef = firebase.storage().ref("Image");
       const fileRef = storageRef.child(file.name)
       fileRef.put(file).then(() => {
           console.log("uploaded", file.name)
      })


  }
   function submit(e) {
       e.preventDefault();
  }
   return (
       <div>
           <form onSubmit={submit}>
               <input type="file" className={style.myfile} onChange={changed} />
           <input type="text" name="name" placeholder="Music name"/>
               <button>Submit</button>
           </form>
           <progress min="0" max="100" value="65"/>
       </div>
  )
}
export default Upload
Enter fullscreen mode Exit fullscreen mode

This creates two input fields: one for text and the other for a file. The onChange event handler in the input file type is used to run a function that uploads whatever file that is added in the input to our firebase storage.

Note that to allow uploads to our Firebase cloud storage, we would need to adjust the rules from our dashboard as shown in the image below.

Storage Rules Changed

Adjusting this rule lets us use our cloud storage without having to be authenticated. This is suitable for development but in the case of deploying it's advisable to use the normal rule.

If we add an image to the input we will see the image in our Firebase Storage within the image folder

uploaded image

After uploading our files to firebase storage, we need to get the URL to these files to reference them in our Firestore database. To do that we will run an async request that will wait until the file has been uploaded to firebase then we will assign the download URL to a variable. Also, we have disabled the button on the form to submit to our database until the file upload is resolved.

function Upload() {
   const [fileUrl, setFileUrl] = React.useState(null)
   const [musicUrl, setMusicUrl] = React.useState(null)
   const [disable, setDisable] = React.useState(true);

  React.useEffect(() => {
    if (musicUrl !== null && fileUrl !== null) {
      setDisable(false);
      alert("click on submit")
      console.log(disable)
    }
  },[musicUrl, fileUrl])
   const filechanged = async (e) =>{
       var file = e.target.files[0];
       var storageRef = firebase.storage().ref("Image");
       const fileRef = storageRef.child(file.name)
       await fileRef.put(file)
       setFileUrl(await fileRef.getDownloadURL())    
  }
   const musicchanged = async (e) =>{
       var music = e.target.files[0];
       var storagemRef = firebase.storage().ref("Music");
       const musicRef = storagemRef.child(music.name)
       await musicRef.put(music)
       setMusicUrl(await musicRef.getDownloadURL())   
  }
 const submit =  (e) => {
    e.preventDefault();  
      const musicname = e.target.musicname.value;
      if (!musicname) {
        return
      }
      db.collection("Music").doc(musicname).set({
        name: musicname,
        music: musicUrl,
        image: fileUrl
      })
      alert("Music added") 
}
   return (
     <div className={style.uploadpage}>
       <form onSubmit={submit} className={style.uploadform}>
         <label>images</label>
         <input
           type="file"
           className={style.myfile}
           name="img"
           onChange={filechanged}
           required
         />
         <label>Music files</label>
         <input type="file" name="music" onChange={musicchanged} required />
         <input
           type="text"
           name="musicname"
           placeholder="Music name"
           required
         />
         <button className={style.btn} disabled={disable}>Submit</button>
       </form>
     </div>
  );
}
export default Upload
Enter fullscreen mode Exit fullscreen mode

We also need to edit the rules for the Firestore database.

We can now add input for the music file, we get an alert when the files have been uploaded telling us to click submit, then we can upload the data to our database when we click the “submit” button. We now see that the record in our database now has a field with the music URL.

Firestore after image and music upload

Add some styles to the library.module.css file to make this page look better.

.uploadpage{
   height: 80vh;
   display: flex;
   justify-content: center;
}
.h{
   text-align: center;
}
.uploadform{
   display: flex;
   flex-direction: column;
   justify-content: center;
   align-items: center;
}
.uploadform input{
   margin: 10px 0 15px 0;
   outline: none;
   padding: 10px;
}
.uploadform input[file]{
   padding: 10px;
}
.uploadform label{
   text-transform: uppercase;
}
.uploadform button{
   border: none;
   padding: 10px 25px;
   background: rgb(73, 13, 236);
   border-radius: 15px;
   color: #fff;
}
Enter fullscreen mode Exit fullscreen mode

We will now proceed to style our search component and add functionality to it.
In Algolia, we can set criteria in configuration for our searchable attributes and the ranking:

searchable attribute

Then for ranking:

ranking

To proceed with our search component:

import React from "react";
import style from "../styles/search.module.css";
import algoliasearch from "algoliasearch/lite";
import { InstantSearch, SearchBox, Hits } from "react-instantsearch-dom";
const searchClient = algoliasearch(
 "algolia id",
 "algolia key"
);
const Hit = ({hit}) => {
 return (<div className={style.hit}>
   <div className={style.artist} onClick={handleClick}>
     <h4>{hit.name}</h4>
   </div>
 </div>)
}
const Content = () => {
 return(<div className={style.content}>
   <Hits hitComponent={Hit}/>
 </div>)
}
function Search() {
 return (
   <div className={style.searchcont}>
     <InstantSearch searchClient={searchClient} indexName="Music">
             <SearchBox translations={{placeholder: 'Search for music'}}/>
       <main>
         <Content/>
       </main>
     </InstantSearch>
   </div>
);
}
export default Search;
Enter fullscreen mode Exit fullscreen mode

Here we rendered a component called Content into the main tag of search. In Content we have the Hits component which renders each hitComponent following the structure we defined in Hit. We created an h4 element which values the hit.name which we got from our database through Algolia.

We will add the click functionality for these individual div items in the onClick event handler.

const Hit = ({ hit }) => {
 const handleClick = () => {
   var playimg = hit.image;
   var playsong = hit.music;
   var title = hit.name;
};
 return (<div className={style.hit}>
   <div className={style.artist} onClick={handleClick}>
     <h4>{hit.name}</h4>
   </div>
 </div>)
}
Enter fullscreen mode Exit fullscreen mode

We will style our search.module.css with the following styles to give it a better appearance.

.searchcont {
  height: 100vh;
  overflow: auto;
  width: 60%;
  padding: 0 10px;
}
.hit {
  background: #333;
  color: #fff;
  padding: 15px 0 15px 10px;
  list-style-type: none;
  border-radius: 5px;
  box-shadow: 6px 6px 8px rgba(0,0,0,.8), -6px -6px 8px rgba(211, 211, 211, 0.151);
  margin: 13px 0;
}

.hit:hover{
  cursor: pointer;
}

.searchcont li {
  list-style-type: none!important;
}
.searchcont ul {
  padding: 0;
}
.search input{
  border: none;
  padding: 10px 15px;
  margin: 10px 5px;
  outline: none;
}
.search button{
  border: none;
  padding: 10px 15px;
  background: #eee;
}
Enter fullscreen mode Exit fullscreen mode

Using the React player to play selected media

We will then delete all dummy data we used for our music source, image and title. Presently in our index.js, we have the following code:

import Head from "next/head";
import Player from "../components/Player"
import Search from "../components/Search"
import { useState } from "react";
import style from "../styles/arrangement.module.css"
export default function Home() {
 return (
   <>
     <head>
       <title>Music Application</title>
       <meta name="keywords" content="music, streaming, entertainment"></meta>
     </head>
   <div className={style.maincont}>
     <Search/>
     <Player />
     </div>
     </>
);
}
Enter fullscreen mode Exit fullscreen mode

You will need to pass the variable for our playimg, playsong, and playtitle to the Player component and use within it. To do this we will first make the variables we just listed states using useState hook since their values will change as our program runs.

...
function Search() {
  const [Image, setImage] = React.useState(null);
  const [title, setTitle] = React.useState(null);
  const [playing, setPlaying] = React.useState(null);
  const searchClient = algoliasearch(
    "algolia id",
    "algolia key"
  );

  const Hit = ({ hit }) => {
    const handleClick = () => {
       setImage(hit.image);
       setPlaying(hit.music);
       setTitle(hit.name);
    };
    return (<div className={style.hit}>
      <div className={style.artist} onClick={handleClick}>
        <h4>{hit.name}</h4>
      </div>
    </div>)
  }
...
Enter fullscreen mode Exit fullscreen mode

Then in our index.js file we will now create state values to store the data we will pull from the search component.

export default function Home(props) {
  const [a, b] = useState(null)
  const [song, newsong] = useState(null)
  const [image, newimage] = useState(null)
  const pull_data = (data) => {
    b(data);
  }
  const pull_song = (data) => {
    newsong(data);
  }
  const pull_img = (data) => {
    newimage(data);
  }

  return (
    <>
      <head>
        <title>Music Application</title>
        <meta name="keywords" content="music, streaming, entertainment"></meta>
      </head>
      <div className={style.maincont}>
        <Search  func={pull_data} song={pull_song} image={pull_img}/>
        <Player title={a} play={song} image={image}/>
      </div>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Here we created and passed props to the Search component which returned values that were assigned to state variables. These state variables were later passed as props down to our Player component. In our Search we assigned values to them as shown below

function Search(props) {
...
  props.func(title);
  props.song(playing)
  props.image(Image)

  const Hit = ({ hit }) => {
    const handleClick = () => {
       setImage(hit.image);
       setPlaying(hit.music);
       setTitle(hit.name);
    };
Enter fullscreen mode Exit fullscreen mode

Here we assigned the props with values of tile, playing, and Image earlier defined, respectively.

We used the passed props in our Player component and passed it down to the Playerdetails and PlayerControls components.

function Player(props) {
    return (
        <div className={style.cplayer}>
            <audio></audio>
            <h4>Playing now</h4>
            <PlayerDetails title={props.title} image={props.image}/>
            <PlayerControls song={props.play}/>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

We could then use the passed props within our playerDetails:

function PlayerDetails(props) {
  return (
    <div className={style.playerdetails}>
      <div className={style.detailsimg}>
        <img src={props.image} alt=""></img>
      </div>
      <h3 className={style.detailstitle}>{props.title}</h3>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Here we have set the title of our h3 to the value passed down in props.title.

We also added src for our music through the props passed to the PlayerControls component.

function PlayerControls(props) {
  return (
    <div className={style.playercontrols}>
      <AudioPlayer
        src={props.song}
        showSkipControls
        autoPlayAfterSrcChange   
      />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Now if we run:

npm run dev
Enter fullscreen mode Exit fullscreen mode

Then navigate to our browser we can play any song we have uploaded.

To make the player image have a constant size we would add some code to the style in our player.module.css.

.detailsimg img{
    display: block;
    margin: 0px auto;
     height: 250px;
    width: 250px;
    border-radius: 50%;
    box-shadow: 6px 6px 12px rgba(0,0,0,.8), -6px -6px 12px rgba(255,255,255,0.4);
}
Enter fullscreen mode Exit fullscreen mode

Here we simply assign height and width attributes to our image to ensure it will always be rounded.

Round image

We can also add a button to navigate to our upload page. To use links in NextJS we need to import it first. We will create a separate file for this in our components folder and call it Nav.js we will then import this file into our Layout component so it can always wrap around all our pages. We will create a new style sheet for this component called nav.module.css and also import it within our file.

import React from 'react'
import Link from "next/link"
import style from "../styles/nav.module.css"
function Nav() {
    return (
        <div className={style.nav}>
            <ul>
                <li>
                    <Link href="/">Home</Link>
                </li>
                <li>
                    <Link href="/library">Upload music</Link>
                </li>
            </ul>
        </div>
    )
}
export default Nav
Enter fullscreen mode Exit fullscreen mode

Then we have the following styles in nav.module.css:

.nav{
    color: #fff;
    display: flex;
    background: #333;
    justify-content: flex-start;
    align-items: center;
    height: 50px;
    padding: 10px;
    margin-left: 10px;
}
.nav ul{
    display: flex;
    justify-content: center;
    align-items: center;
    list-style: none;
}
.nav ul li a{
    margin: 5px 15px;
}
Enter fullscreen mode Exit fullscreen mode

We will add this new component to our Layout

const Layout = ({children}) => {
  return (
    ...
        <Nav/>
          </div>
          {children}
      <div className={style.footer}>
        <h3>Browse and listen to music of your choice</h3>
      </div>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Now we have the navigation bar displaying on both our pages.

added navigation bar

Conclusion

In this tutorial we covered building a music entertainment application with different technologies. We learned what these technologies were and their uses in the world today.

Resources

Link to Github resource: Github

Discussion (1)

Collapse
mena234 profile image
Mena Nasser

Really awesome tutorial, thank u bro