OpenReplay
Navigate back to the homepage
BLOG
Browse Repo
Back

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

Tuduo Victory
October 21st, 2021 · 12 min read

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:

1npx install create-next-app musicapp --use-npm

for yarn users:

1npx install create-next-app musicapp

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:

1import Head from 'next/head'
2export default function Home() {
3 return (
4 <div >
5 <h1>components here</h1>
6 </div>
7)
8}

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

1import Head from 'next/head'
2export default function Home() {
3 return (
4 <div>
5 <head>
6 <title>Music Application</title>
7 <meta name="keywords" content="music, streaming, entertainment"></meta>
8 </head>
9 <h1>components here</h1>
10 </div>
11);
12}

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:

1import React from "react";
2import Head from 'next/head'
3const library = () => {
4 return (
5 <div>
6 <head>
7 <title>Library</title>
8 <meta name="keywords" content="music, streaming, entertainment"></meta>
9 </head>
10 <h1>Music LIbrary here:</h1>
11 </div>
12);
13};
14export default library;

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:

1npm run dev

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:

1import {UseState} from 'react';

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 use``S``tate react hook as follows:

1export default function Home() {
2 const [songs, setsongs] = useState([
3 {
4 title: "song 1",
5 artist: "artist 1",
6 img_src: "./images/img1.jpg",
7 src: "./songs/Måneskin - Beggin ( Testo)_2.mp3",
8 },
9 {
10 title: "song 2",
11 artist: "artist 2",
12 img_src: "./images/img2.jpg",
13 src: "./songs/Young Dumb & Broke Khalid .mp3",
14 },
15]);
16 return (
17 <div>
18 <head>
19 <title>Music Application</title>
20 <meta name="keywords" content="music, streaming, entertainment"></meta>
21 </head>
22 <h1>components here</h1>
23 </div>
24);
25}

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:

1import React from "react";
2import style from "../styles/layout.module.css";
3const Layout = ({children}) => {
4 return (
5 <div className={style.container}>
6 <div className={style.top}>
7 <h3>Music Streaming</h3>
8 </div>
9 {children}
10 <div className={style.footer}>
11 <h3>Browse and listen to music of your choice</h3>
12 </div>
13 </div>
14);
15};
16export default Layout;

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

1.container {
2 font-weight: bold;
3 color: #333;
4}
5.top,
6.footer {
7 height: 50px;
8 width: 100%;
9 color:#fff;
10 background: rgb(73, 13, 236);
11 display: flex;
12 align-items: center;
13 padding-left: 15px;
14}

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

1import Layout from '../components/Layout'
2import '../styles/globals.css'
3function MyApp({ Component, pageProps }) {
4 return (
5 <Layout>
6 <Component {...pageProps} />
7 </Layout>
8);
9}
10export default MyApp

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:

1import React from 'react'
2import style from "../styles/player.module.css"
3function Player() {
4 return (
5 <div className={style.cplayer}>
6 <audio></audio>
7 <h4>Playing now</h4>
8 {/*music search functionality */}
9 {/*player Details here */}
10 {/*plaer controls here */}
11 <p><strong>Next up: </strong>next song here</p>
12 </div>
13 )
14}
15export default Player

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

1const [currentSongIndex, setCurrentSongIndex] = useState(0);
2const [nextSongIndex, setNextSongIndex] = useState(currentSongIndex + 1);

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

1import Player from "../components/Player"

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.

1import Head from "next/head";
2import Player from "../components/Player"
3import { useState } from "react";
4export default function Home() {
5 ...
6 const [currentSongIndex, setCurrentSongIndex] = useState(0);
7 const [nextSongIndex, setNextSongIndex] = useState(currentSongIndex + 1);
8 return (
9 <div>
10 <head>
11 <title>Music Application</title>
12 <meta name="keywords" content="music, streaming, entertainment"></meta>
13 </head>
14 <h1>components here</h1>
15 <Player
16 song={songs[currentSongIndex]}
17 nextSong={songs[nextSongIndex]}
18 />
19 </div>
20);
21}

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.

1function Player(props) {
2 return (
3 <div className={style.cplayer}>
4 <audio></audio>
5 <h4>Playing now</h4>
6 {/*music search functionality */}
7 {/*player Details here */}
8 {/*plaer controls here */}
9 <p><strong>Next up: </strong>{props.nextSong.title} by {props.nextSong.artist}</p>
10 </div>
11 )
12}

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:

1import React from 'react'
2import style from "../styles/player.module.css"
3function PlayerDetails(props) {
4 return (
5 <div className={style.playerdetails}>
6 <div className={style.detailsimg}>
7 <img src={props.song.img_src} alt=""></img>
8 </div>
9 <h3 className={style.detailstitle}>{props.song.title}</h3>
10 <h3 className={style.detailsartist}>{props.song.artist}</h3>
11 </div>
12 )
13}
14export default PlayerDetails

We will then import this within our Player.js component

1import PlayerDetails from './PlayerDetails'

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

1function Player(props) {
2 return (
3 <div className={style.cplayer}>
4 <audio></audio>
5 <h4>Playing now</h4>
6 {/*music search functionality */}
7 <PlayerDetails song={props.song}/>
8 {/*plaer controls here */}
9 <p><strong>Next up: </strong>{props.nextSong.title} by {props.nextSong.artist}</p>
10 </div>
11 )
12}

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:

1npm i react-h5-audio-player

for yarn users:

1yarn add react-h5-audio-player

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.

1import React from "react";
2import style from "../styles/player.module.css";
3import AudioPlayer from "react-h5-audio-player";
4import "react-h5-audio-player/lib/styles.css";
5function PlayerControls(props) {
6 return (
7 <div className={style.playercontrols}>
8 <AudioPlayer
9 autoPlay
10 src={props.song.src}
11 onPlay={(e) => console.log("onPlay")}
12 // other props here
13 showSkipControls
14 autoPlayAfterSrcChange
15
16 />
17 </div>
18);
19}
20export default PlayerControls;

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

1function Player(props) {
2 return (
3 <div className={style.cplayer}>
4 <audio></audio>
5 <h4>Playing now</h4>
6 {/*music search functionality */}
7 <PlayerDetails song={props.song}/>
8 <PlayerControls song={props.song}/>
9 <p><strong>Next up: </strong>{props.nextSong.title} by {props.nextSong.artist}</p>
10 </div>
11 )
12}

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:

1import React from 'react'
2import style from "../styles/search.module.css"
3function Search() {
4 return (
5 <div className={style.searchcont}>
6 {/*search*/}
7 </div>
8 )
9}
10export default Search

In our search.module.css file we have:

1.searchcont{
2 height:100%;
3 width: 60%;
4 background: #ddd;
5}

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.

1import Head from "next/head";
2import Player from "../components/Player"
3import Search from "../components/Search"
4import { useState } from "react";
5import style from "../styles/arrangement.module.css"
6export default function Home() {
7...
8 return (
9 <div className={style.maincont}>
10 <head>
11 <title>Music Application</title>
12 <meta name="keywords" content="music, streaming, entertainment"></meta>
13 </head>
14 <Search/>
15 <Player
16 song={songs[currentSongIndex]}
17 nextSong={songs[nextSongIndex]}
18 />
19 </div>
20);
21}

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

1.maincont{
2 display: flex;
3}

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

1.cplayer{
2 margin: 0;
3 box-sizing: border-box;
4 font-family: monospace;
5 background: #313131;
6 color:#fff;
7 width: 100%;
8 display: flex;
9 align-items: center;
10 justify-content: center;
11 min-height: 100vh;
12 flex-direction: column;
13 border-top-left-radius: 13px;
14 border-bottom-left-radius: 13px;
15 padding: 50px;
16 padding-top: 3px;
17 /* box-shadow: inset -6px -6px 12px rgba(0,0,0,.8); */
18}
19.cplayer h4{
20 font-size: 14px;
21 text-transform: uppercase;
22 font-weight: 400;
23 text-align: center;
24}
25.cplayer > p{
26 color: #aaa;
27 font-size: 14px;
28 text-align: center;
29 font-weight: 400;
30}
31.playerdetails .detailsimg{
32 position: relative;
33 width: fit-content;
34 margin: 0 auto;
35}
36.detailsimg img{
37 display: block;
38 margin: 0px auto;
39 width: 100%;
40 max-width: 250px;
41 border-radius: 50%;
42 box-shadow: 6px 6px 12px rgba(0,0,0,.8), -6px -6px 12px rgba(255,255,255,0.4);
43}
44.detailstitle{
45 color: #eee;
46 font-size: 20px;
47 text-shadow: 2px 2px 4px rgba(0,0,0,.8), -2px -2px 4px rgba(255,255,255,0.4);
48 text-align: center;
49 margin-bottom: 10px;
50}
51.detailsartist{
52 color: #aaa;
53 font-size: 20px;
54 text-shadow: 2px 2px 4px rgba(0,0,0,.8), -2px -2px 4px rgba(255,255,255,0.4);
55 text-align: center;
56 margin-bottom: 20px;
57}

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:

1npm install --save firebase
2npm install -g firebase-tools
3firebase init functions

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:

1cd functions

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

1firebase functions:config:set algolia.app=Your_app_id algolia.key=admin_api_key

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.

1npm install faker

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.

null

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.

1const admin = require("firebase-admin");
2var serviceAccount = require("path to your key");
3paste admin.intializeApp code here
4const faker = require("faker");
5const db = admin.firestore();
6const fakeIt = () => {
7 return db.collection("Music").add({
8 username: faker.internet.userName(),
9 email: faker.internet.email(),
10 avatar: faker.internet.avatar(),
11});
12};
13Array(5).fill(0).forEach(fakeIt);

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

1node seed.js

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:

1npm install algoliasearch react-instantsearch-dom react-instantsearch

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

1import React from "react";
2import style from "../styles/search.module.css";
3import algoliasearch from "algoliasearch/lite";
4import { InstantSearch, SearchBox, Hits } from "react-instantsearch-dom";
5const searchClient = algoliasearch(
6 "algolia id",
7 "algolia key"
8);
9function Search() {
10 return (
11 <div className={style.searchcont}>
12 <InstantSearch searchClient={searchClient} indexName="Music">
13 <SearchBox translations={{placeholder: 'Search for music'}}/>
14 <Hits/>
15 </InstantSearch>
16 </div>
17);
18}
19export default Search;

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:

1import Upload from "../components/Upload"
2import style from "../styles/library.module.css"

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:

1import firebase from "firebase";
2import "firebase/firestore";
3const firebaseConfig = {
4//you will find this in your project settings on firebase
5};
6!firebase.apps.length
7 ? firebase.initializeApp(firebaseConfig).firestore()
8: firebase.app().firestore();
9var db = firebase.firestore();
10export default db

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:

1import React from "react";
2import style from "../styles/library.module.css";
3import db from "../firebase"
4import firebase from "firebase";
5import "firebase/storage"
6function Upload() {
7 const changed = (e) =>{
8 var file = e.target.files[0];
9 var storageRef = firebase.storage().ref("Image");
10 const fileRef = storageRef.child(file.name)
11 fileRef.put(file).then(() => {
12 console.log("uploaded", file.name)
13 })
14
15
16 }
17 function submit(e) {
18 e.preventDefault();
19 }
20 return (
21 <div>
22 <form onSubmit={submit}>
23 <input type="file" className={style.myfile} onChange={changed} />
24 <input type="text" name="name" placeholder="Music name"/>
25 <button>Submit</button>
26 </form>
27 <progress min="0" max="100" value="65"/>
28 </div>
29 )
30}
31export default Upload

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.

1function Upload() {
2 const [fileUrl, setFileUrl] = React.useState(null)
3 const [musicUrl, setMusicUrl] = React.useState(null)
4 const [disable, setDisable] = React.useState(true);
5
6 React.useEffect(() => {
7 if (musicUrl !== null && fileUrl !== null) {
8 setDisable(false);
9 alert("click on submit")
10 console.log(disable)
11 }
12 },[musicUrl, fileUrl])
13 const filechanged = async (e) =>{
14 var file = e.target.files[0];
15 var storageRef = firebase.storage().ref("Image");
16 const fileRef = storageRef.child(file.name)
17 await fileRef.put(file)
18 setFileUrl(await fileRef.getDownloadURL())
19 }
20 const musicchanged = async (e) =>{
21 var music = e.target.files[0];
22 var storagemRef = firebase.storage().ref("Music");
23 const musicRef = storagemRef.child(music.name)
24 await musicRef.put(music)
25 setMusicUrl(await musicRef.getDownloadURL())
26 }
27 const submit = (e) => {
28 e.preventDefault();
29 const musicname = e.target.musicname.value;
30 if (!musicname) {
31 return
32 }
33 db.collection("Music").doc(musicname).set({
34 name: musicname,
35 music: musicUrl,
36 image: fileUrl
37 })
38 alert("Music added")
39}
40 return (
41 <div className={style.uploadpage}>
42 <form onSubmit={submit} className={style.uploadform}>
43 <label>images</label>
44 <input
45 type="file"
46 className={style.myfile}
47 name="img"
48 onChange={filechanged}
49 required
50 />
51 <label>Music files</label>
52 <input type="file" name="music" onChange={musicchanged} required />
53 <input
54 type="text"
55 name="musicname"
56 placeholder="Music name"
57 required
58 />
59 <button className={style.btn} disabled={disable}>Submit</button>
60 </form>
61 </div>
62 );
63}
64export default Upload

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

null

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.

1.uploadpage{
2 height: 80vh;
3 display: flex;
4 justify-content: center;
5}
6.h{
7 text-align: center;
8}
9.uploadform{
10 display: flex;
11 flex-direction: column;
12 justify-content: center;
13 align-items: center;
14}
15.uploadform input{
16 margin: 10px 0 15px 0;
17 outline: none;
18 padding: 10px;
19}
20.uploadform input[file]{
21 padding: 10px;
22}
23.uploadform label{
24 text-transform: uppercase;
25}
26.uploadform button{
27 border: none;
28 padding: 10px 25px;
29 background: rgb(73, 13, 236);
30 border-radius: 15px;
31 color: #fff;
32}

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:

1import React from "react";
2import style from "../styles/search.module.css";
3import algoliasearch from "algoliasearch/lite";
4import { InstantSearch, SearchBox, Hits } from "react-instantsearch-dom";
5const searchClient = algoliasearch(
6 "algolia id",
7 "algolia key"
8);
9const Hit = ({hit}) => {
10 return (<div className={style.hit}>
11 <div className={style.artist} onClick={handleClick}>
12 <h4>{hit.name}</h4>
13 </div>
14 </div>)
15}
16const Content = () => {
17 return(<div className={style.content}>
18 <Hits hitComponent={Hit}/>
19 </div>)
20}
21function Search() {
22 return (
23 <div className={style.searchcont}>
24 <InstantSearch searchClient={searchClient} indexName="Music">
25 <SearchBox translations={{placeholder: 'Search for music'}}/>
26 <main>
27 <Content/>
28 </main>
29 </InstantSearch>
30 </div>
31);
32}
33export default Search;

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.

null

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

1const Hit = ({ hit }) => {
2 const handleClick = () => {
3 var playimg = hit.image;
4 var playsong = hit.music;
5 var title = hit.name;
6};
7 return (<div className={style.hit}>
8 <div className={style.artist} onClick={handleClick}>
9 <h4>{hit.name}</h4>
10 </div>
11 </div>)
12}

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

1.searchcont {
2 height: 100vh;
3 overflow: auto;
4 width: 60%;
5 padding: 0 10px;
6}
7.hit {
8 background: #333;
9 color: #fff;
10 padding: 15px 0 15px 10px;
11 list-style-type: none;
12 border-radius: 5px;
13 box-shadow: 6px 6px 8px rgba(0,0,0,.8), -6px -6px 8px rgba(211, 211, 211, 0.151);
14 margin: 13px 0;
15}
16
17.hit:hover{
18 cursor: pointer;
19}
20
21.searchcont li {
22 list-style-type: none!important;
23}
24.searchcont ul {
25 padding: 0;
26}
27.search input{
28 border: none;
29 padding: 10px 15px;
30 margin: 10px 5px;
31 outline: none;
32}
33.search button{
34 border: none;
35 padding: 10px 15px;
36 background: #eee;
37}

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:

1import Head from "next/head";
2import Player from "../components/Player"
3import Search from "../components/Search"
4import { useState } from "react";
5import style from "../styles/arrangement.module.css"
6export default function Home() {
7 return (
8 <>
9 <head>
10 <title>Music Application</title>
11 <meta name="keywords" content="music, streaming, entertainment"></meta>
12 </head>
13 <div className={style.maincont}>
14 <Search/>
15 <Player />
16 </div>
17 </>
18);
19}

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.

1...
2function Search() {
3 const [Image, setImage] = React.useState(null);
4 const [title, setTitle] = React.useState(null);
5 const [playing, setPlaying] = React.useState(null);
6 const searchClient = algoliasearch(
7 "algolia id",
8 "algolia key"
9 );
10
11 const Hit = ({ hit }) => {
12 const handleClick = () => {
13 setImage(hit.image);
14 setPlaying(hit.music);
15 setTitle(hit.name);
16 };
17 return (<div className={style.hit}>
18 <div className={style.artist} onClick={handleClick}>
19 <h4>{hit.name}</h4>
20 </div>
21 </div>)
22 }
23...

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

1export default function Home(props) {
2 const [a, b] = useState(null)
3 const [song, newsong] = useState(null)
4 const [image, newimage] = useState(null)
5 const pull_data = (data) => {
6 b(data);
7 }
8 const pull_song = (data) => {
9 newsong(data);
10 }
11 const pull_img = (data) => {
12 newimage(data);
13 }
14
15 return (
16 <>
17 <head>
18 <title>Music Application</title>
19 <meta name="keywords" content="music, streaming, entertainment"></meta>
20 </head>
21 <div className={style.maincont}>
22 <Search func={pull_data} song={pull_song} image={pull_img}/>
23 <Player title={a} play={song} image={image}/>
24 </div>
25 </>
26 );
27}

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

1function Search(props) {
2...
3 props.func(title);
4 props.song(playing)
5 props.image(Image)
6
7 const Hit = ({ hit }) => {
8 const handleClick = () => {
9 setImage(hit.image);
10 setPlaying(hit.music);
11 setTitle(hit.name);
12 };

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.

1function Player(props) {
2 return (
3 <div className={style.cplayer}>
4 <audio></audio>
5 <h4>Playing now</h4>
6 <PlayerDetails title={props.title} image={props.image}/>
7 <PlayerControls song={props.play}/>
8 </div>
9 )
10}

We could then use the passed props within our playerDetails:

1function PlayerDetails(props) {
2 return (
3 <div className={style.playerdetails}>
4 <div className={style.detailsimg}>
5 <img src={props.image} alt=""></img>
6 </div>
7 <h3 className={style.detailstitle}>{props.title}</h3>
8 </div>
9 );
10}

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.

1function PlayerControls(props) {
2 return (
3 <div className={style.playercontrols}>
4 <AudioPlayer
5 src={props.song}
6 showSkipControls
7 autoPlayAfterSrcChange
8 />
9 </div>
10 );
11}

Now if we run:

1npm run dev

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

null

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

1.detailsimg img{
2 display: block;
3 margin: 0px auto;
4 height: 250px;
5 width: 250px;
6 border-radius: 50%;
7 box-shadow: 6px 6px 12px rgba(0,0,0,.8), -6px -6px 12px rgba(255,255,255,0.4);
8}

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.

1import React from 'react'
2import Link from "next/link"
3import style from "../styles/nav.module.css"
4function Nav() {
5 return (
6 <div className={style.nav}>
7 <ul>
8 <li>
9 <Link href="/">Home</Link>
10 </li>
11 <li>
12 <Link href="/library">Upload music</Link>
13 </li>
14 </ul>
15 </div>
16 )
17}
18export default Nav

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

1.nav{
2 color: #fff;
3 display: flex;
4 background: #333;
5 justify-content: flex-start;
6 align-items: center;
7 height: 50px;
8 padding: 10px;
9 margin-left: 10px;
10}
11.nav ul{
12 display: flex;
13 justify-content: center;
14 align-items: center;
15 list-style: none;
16}
17.nav ul li a{
18 margin: 5px 15px;
19}

We will add this new component to our Layout

1const Layout = ({children}) => {
2 return (
3 ...
4 <Nav/>
5 </div>
6 {children}
7 <div className={style.footer}>
8 <h3>Browse and listen to music of your choice</h3>
9 </div>
10 </div>
11 );
12};

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

More articles from OpenReplay Blog

TypeScript Done Wrong

Top 5 mistakes we've all done at one point or another

October 20th, 2021 · 8 min read

13 of Darkest Design Patterns You Can Find on Internet

Some of the scariest design patterns found online, are you guilty of some of them?

October 14th, 2021 · 6 min read
© 2021 OpenReplay Blog
Link to $https://twitter.com/OpenReplayHQLink to $https://github.com/openreplay/openreplayLink to $https://www.linkedin.com/company/18257552