Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added music player with spotify api #15

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions api/routes/music.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
const express = require('express');
const request = require('request');
const dotenv = require('dotenv');

const port = 5000;
const app = express();

global.access_token = ''

dotenv.config();

var spotify_client_id = process.env.SPOTIFY_CLIENT_ID;
var spotify_client_secret = process.env.SPOTIFY_CLIENT_SECRET;

var spotify_redirect_uri = "http://localhost:3000/auth/callback";

const generateState = (length) => {
let state = '';
const keys = "ABCDEFGHIJKLOMNPQRSTUVWXYZabcdefghijklomnpqrstuvwxyz123456789!@#$%^&*()";

for (let i = 0; i < length; i++) {
state += keys[Math.floor(Math.random() * keys.length)];
}

return state
}

// request authorization token
app.get('/auth/login', (req, res) => {
var scope = "streaming user-read-email user-read-private";

// these are the necessary parameters for the request direct
var auth_query_parameters = new URLSearchParams({
response_type: "code",
client_id: spotify_client_id,
scope: scope,
redirect_uri: spotify_redirect_uri,
state: generateState(16)
})

// returns a code and state for the next request for the access token
res.redirect('https://accounts.spotify.com/authorize/?' + auth_query_parameters.toString());
});

// request access token with the response from the authorization token
app.get('/auth/callback', (req, res) => {
// from the previous request
var code = req.query.code;

// parameters needed for the access token request
var authOptions = {
url: "https://accounts.spotify.com/api/token",
form: {
code: code,
redirect_uri: spotify_redirect_uri,
grant_type: "authorization_code"
},
headers: {
'Authorization': 'Basic ' + (Buffer.from(spotify_client_id + ":" + spotify_client_secret).toString('base64')),
'Content-Type' : 'application/x-www-form-urlencoded'
},
json: true
};

// on response it will return the access token and you can direct to the root
request.post(authOptions, (error, response, body) => {
if (!error && response.statusCode === 200) {
access_token = body.access_token;
res.redirect('/');
}
})
});

app.get('/refresh_token', (req, res) => {
var refresh_token = req.query.refresh_token;
var authOptions = {
url: 'https://accounts.spotify.com/api/token',
headers: {
'Authorization': 'Basic ' + (new Buffer.from(client_id + ":" + client_secret).toString('base64')) },
form: {
grant_type: 'refresh_token',
refresh_token: refresh_token
},
json: true
}

request.post(authOptions, (error, response, body) => {
if (!error && response.statusCode === 200) {
var access_token = body.access_token;
res.send({ 'access_token': access_token })
}
})
})

// returns access token from the request as json to the /auth/token endpoint
// which will allow us to start the music player and make API calls and retrieve important data
app.get('/auth/token', (req, res) => {
res.json({access_token: access_token})
})

app.listen(port, () => {
console.log(`Listening at http://localhost:${port}`)
});
25 changes: 25 additions & 0 deletions client/src/components/Music-Player/LoginPlayer.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
.loginWrapper {
background-color: #1ED760;
height: 50px;
width: 200px;
border-radius: 15px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}

.loginWrapper:hover {
opacity: 0.8;
transition: 0.1;
}

.loginWrapper:active {
opacity: 0.7;
transition: 0.1;
}

.loginSpotify {
color: black;
text-decoration: none;
}
12 changes: 12 additions & 0 deletions client/src/components/Music-Player/LoginPlayer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';
import './LoginPlayer.css';

function LoginPlayer() {
return (
<div className='loginWrapper'>
<a className="loginSpotify" href="/auth/login">Login with Spotify</a>
</div>
)
}

export default LoginPlayer;
52 changes: 52 additions & 0 deletions client/src/components/Music-Player/MusicControl.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
.MusicPlayer {
background-color: #808080;
height: 200px;
width: 400px;
border-radius: 15px;
display: flex;
flex-direction: column;
}

.trackDisplay {
font-size: 12px;
display: flex;
flex: 2;
align-items: center;
justify-content: space-evenly;
overflow-wrap: break-word;
}

.trackImage {
height: 100px;
width: 100px;
border-radius: 10px;
}

.playbackElement:hover {
opacity: 0.5;
transition: 0.2;
}

.playbackElement:active {
opacity: 0.4;
transition: 0.2;
}

.playbackControl {
display: flex;
justify-content: center;
flex: 1
}

.progressContainer {
font-size: 10px;
display: flex;
justify-content: space-evenly;
align-items: center;
margin-bottom: 10px;
}

.trackProgress {
width: 80%;
}

174 changes: 174 additions & 0 deletions client/src/components/Music-Player/MusicControl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import { useState, useEffect } from 'react';
import { LinearProgress } from '@mui/material';
import PauseIcon from '@mui/icons-material/Pause';
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
import SkipNextIcon from '@mui/icons-material/SkipNext';
import SkipPreviousIcon from '@mui/icons-material/SkipPrevious';
import './MusicControl.css'

function MusicControl({ token }) {
const [player, setPlayer] = useState(null);
const [track, setTrack] = useState(null);
const [state, setState] = useState(false);
const [paused, setPaused] = useState(false);
const [position, setPosition] = useState(0);
const [currentTime, setCurrentTime] = useState({min: 0, sec: 0});
const [duration, setDuration] = useState({min: 0, sec: 0});
const [msDuration, setmsDuration] = useState(0);
const [progress, setProgress] = useState(0);
const [WebPlaybackPlayer, setWebPlaybackPlayer] = useState(null);

const msToMinsSecs = (ms) => {
var min = Math.floor(ms / 60000);
var sec = Math.floor((ms % 60000) / 1000);
return {min, sec}
}

function secondsToTime(s) {
var min = Math.floor((s % 3600) / 60);
var sec = s % 60;
return {min, sec}
}

useEffect(() => {
const script = document.createElement("script");
script.src = "https://sdk.scdn.co/spotify-player.js";
script.async = true;

document.body.appendChild(script);

window.onSpotifyWebPlaybackSDKReady = () => {
const player = new window.Spotify.Player({
name: 'Web Playback SDK Quick Start Player',
getOAuthToken: cb => { cb(token); },
volume: 0.5
});

setPlayer(player);

player.addListener('ready', ({ device_id }) => {
console.log("Ready with Device ID", device_id);
})

player.addListener('not_ready', ({ device_id }) => {
console.log("Device ID is not ready for playback", device_id);
})

player.addListener('initialization_error', ({ message }) => {
console.error("Failed to initialize", message);
})

player.addListener('authentication_error', ({ message }) => {
console.error("Authentication failed", message);
})

player.addListener('account_error', ({ message }) => {
console.error("Account error", message);
})

player.addListener('player_state_changed', (state => {
if (!state) return;

setTrack(state.track_window.current_track);
setPaused(state.paused);
setWebPlaybackPlayer(state);

if (state.position === 0) {
setPosition(state.position);
setCurrentTime(secondsToTime(state.position));
setDuration(msToMinsSecs(state.duration));
setmsDuration(state.duration);
}

player.getCurrentState().then(state => {
state ? setState(true) : setState(false)
});
}));

player.connect();

};
}, []);

useEffect(() => {
const trackProgression = setInterval(() => {
if (track && !paused) {
let msPosition = position * 1000;
let progressPercentage = Math.round((msPosition / msDuration) * 100)

setPosition(position + 1);
setCurrentTime(secondsToTime(position));
setProgress(progressPercentage);
}

}, 1000)

return () => {
clearInterval(trackProgression)
}

}, [position, currentTime, WebPlaybackPlayer, paused])

if (!state) {
return (
<>
Play on Spotify
</>
)
} else {
return (
<>
<div className='MusicPlayer'>
<div className='trackDisplay'>
<img
className="trackImage"
src={track.album.images[0].url} />

{track.name} <br/>
{track.artists[0].name}
</div>

<div className='progressContainer'>
{currentTime.min} : {currentTime.sec.toString().padStart(2, '0')}
<LinearProgress
className='trackProgress'
variant='determinate'
value={progress}
/>
{duration.min} : {duration.sec.toString().padStart(2, '0')}
</div>

<div className='playbackControl'>
<SkipPreviousIcon
className='playbackElement'
sx={{fontSize: 35}}
onClick={() => {player.previousTrack()}}
/>

{paused ?
<PlayArrowIcon
className='playbackElement'
sx={{fontSize: 35}}
onClick={() => {player.togglePlay()}}
/> :
<PauseIcon
className='playbackElement'
sx={{fontSize: 35}}
onClick={() => {player.togglePlay()}}
/>
}


<SkipNextIcon
className='playbackElement'
sx={{fontSize: 35}}
onClick={() => {player.nextTrack()}}
/>
</div>
</div>
</>
)
}
}

export default MusicControl;
23 changes: 23 additions & 0 deletions client/src/components/Music-Player/MusicPlayer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React, { useState, useEffect } from 'react'
import LoginPlayer from './LoginPlayer.js'
import MusicControl from './MusicControl.js'
import './MusicPlayer.css';

function MusicPlayer() {
const [token, setToken] = useState('');

useEffect(() => {
async function getToken() {
const response = await fetch('/auth/token');
const json = await response.json();
setToken(json.access_token);
}
getToken();
}, [])

return (
token === "" ? <LoginPlayer/> : <MusicControl token={token} />
)
}

export default MusicPlayer;
Loading