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
for yarn users:
npx 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:
import Head from 'next/head'
export default function Home() {
return (
<div >
<h1>components here</h1>
</div>
)
}
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>
);
}
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;
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
Navigate to the URL our server is hosted on, we see the homepage as our index.js
component.
Adding "/library" to our URL will redirect you to our library.js
component:
Back in our index.js
file. we will be using the react useState
hook:
import {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:
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>
);
}
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;
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;
}
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
If we return to our browser, on our main page we will have the output below:
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
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);
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"
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>
);
}
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>
)
}
If you return to your browser you will have an output similar to the image below:
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
We will then import this within our Player.js
component
import PlayerDetails from './PlayerDetails'
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>
)
}
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
for yarn users:
yarn 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.
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;
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>
)
}
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
In our search.module.css
file we have:
.searchcont{
height:100%;
width: 60%;
background: #ddd;
}
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>
);
}
In the arrangement.module.css
we have the following styles:
.maincont{
display: flex;
}
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;
}
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
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
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
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
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);
To run this, open the command shell window and key in:
node 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.
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
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;
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.
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"
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
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
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.
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
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
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
.
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;
}
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:
Then for 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;
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>)
}
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;
}
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>
</>
);
}
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>)
}
...
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>
</>
);
}
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);
};
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>
)
}
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>
);
}
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>
);
}
Now if we run:
npm run dev
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);
}
Here we simply assign height and width attributes to our image to ensure it will always be rounded.
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
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;
}
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>
);
};
Now we have the navigation bar displaying on both our pages.
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
Top comments (1)
Really awesome tutorial, thank u bro