Skip to content

TimothyJan/Music-Controller-Web-App

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

40 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Music-Controller-Web-App

Collaborative music playing full stack web app integrated with the third party Spotify API using Python(Django/Django Rest Framework) and Javascript(React)

Host creates a room for people to listen to Spotify music. Host controls the guests permissions such as how many votes are needed to skip the song and whether guests can pause/play songs. Guests can join the room using the room code. Spotify Premium required.

Django & React - Full Stack Web App with Python & Javascript from Tech With Tim.

Home Page.

Home Page

Create a Room Page. Modify setting accordingly.

CreateRoomPage

Login to Spotify.

SpotifyLogin Page

Room Page with code to join room.

RoomPage

Room Join Page - enter the room code and click "ENTER ROOM" button.

RoomJoinPage

Guests can vote to skip if host has permitted them.

RoomPagewSkip

As the host, click the "SETTINGS" button, update settings and click the "UPDATE" button.

UpdateRoom

Home Page with room settings updated.

UpdatedRoom

Notes:
Tutorial 1 - Full Stack Web App with Python & JS:

  • ~pip install django djangorestframework
  • ~django-admin startproject music-controller
  • cd to music_controller directory and ~django-admin startapp api
  • Add 'api.apps.ApiConfig' and 'rest_framework' to INSTALLED_APPS in settings.py of music_controller project to add add to the project. ApiConfig is from the apps.py in api app
  • Create urls.py file in api app to store URLs local to this app. Create urls.py file in music_controller project
  • Include api.urls to the url_patterns in urls.py in the music_controller project.
  • Add views to the url_patterns in urls.py in the api app
  • In music_controller project directory, ~python manage.py makemigrations to update the database and store current changes made to the app
  • ~python manage.py migrate
  • ~python manage.py runserver

Tutorial 2 - Django REST Framework:

  • Create Room model in models.py in api app
  • In music_controller project directory, ~python manage.py makemigrations to update the database and store current changes made to the app and ~python manage.py migrate
  • Create serializers.py in api app. This will take our Python related code and translate it into a JSON response. Add RoomSerializer class to serializers.py
  • generics.CreateAPIView api view class RoomView. generics allows us to create a class that inherits from an api view.
  • ~python manage.py runserver and add data
  • generics.ListAPIView api view class RoomView will list out the data

Tutorial 3 - React Integration Using Webpack & Babel:

  • cd to music_controller project and ~django-admin startapp frontend
  • In frontend app, create templates, src and static folders. static holds static files, anything our browser would cache. In static folder create frontend, css and images folders. In src folder create components folder.
  • Add 'frontend.apps.FrontendConfig' to INSTALLED_APPS in setting.py of music_controller/music_controller
  • cd to music_controller\frontend and perform installations
    • ~npm init -y
    • Then install webpack ~npm i webpack webpack-cli --save-dev. Webpack is a free and open-source module bundler for JavaScript. It is made primarily for JavaScript, but it can transform front-end assets such as HTML, CSS, and images if the corresponding loaders are included. Webpack takes modules with dependencies and generates static assets representing those modules.
    • Then install Babel ~npm i @babel/core babel-loader @babel/preset-env @babel/preset-react --save-dev, Babel takes our code and transpiles it into code that is friendly with all browsers such as ES6 or ES7 Javascript code
    • Then install react ~npm i react react-dom
    • Then install material UI ~npm install @material-ui/core
    • To use async and await in our Javascript code install ~npm install @babel/plugin-proposal-class-properties
    • To reroute our pages install ~npm install react-router-dom
    • To use icons from Material UI install ~npm install @material-ui/icons
  • Create babel.config.json file in music_controller/frontend. Set up Babel loader and uses environment presets thats targetting node version 10 and @babel/preset-react because we are using react. Plugins so we can use async and await
  • Create webpack.config.js. Webpack will bundle all our Javascript into one file and serve that bundle to the browser
  • Update package.json scripts with webpack running in development and watch mode. and build script with webpack in production mode
  • We want Django to render a page that react will take control of
  • Inside templates, create folder frontend. Inside frontend create index.html
  • Inside view.py of music_controller\frontend create index function to render the index template and let react take care of it
  • Create a urls.py for music_controller\frontend
  • Inside urls.py for music_controller\music_controller add url for frontend
  • Create new component App.js inside components folder
  • Inside src folder, create index.js and import App from components/App
  • Check that server is running in music_controller ~python manage.py runserver
  • cd to music_controller\frontend and run ~npm run dev
  • The main.js inside static\frontend now contains the bundled up Javascript

Tutorial 4 - React Router and Building Components:

  • Create an index.css in static\css folder
  • cd to music_controller\frontend and run ~npm run dev. cd to music_controller\frontend and run ~python manage.py runserver
  • Create new components HomePage.js RoomJoinPage.js CreateRoomPage.js
  • As of late 2021, react dom v6 switch is replaced by "Routes". To use TechwithTim code, do ~npm uninstall react-router-dom and then npm install react-router-dom@5.2.0
  • Update urls.py of music_controller/frontend with urls for react components

Tutorial 5 - Handling POST Requests (Django REST):

  • in views.py of api app import following
    • from rest_framework.views import APIview for generic apiview
    • from rest_framework.response import Response to send custom responses from our view
    • from rest_framework import status to give us access to HTTP status codes, which we will need to use when use send our custom responses
  • Create a new serializer class CreateRoomSerializer in serializers.py of api app. We will send a POST request to the endpoint. This serializer will make sure the payload of the POST request corresponds with the correct fields that we need to create a new room.
  • Create new view class CreateRoomView in views.py of api app. use session_key.
  • Whenever we connect to a website we establish a session. A session is s temporary connection between 2 computers/devices. ex) don't have to sign into FB later because using the same session, everything authenticated already. Sessions have unique identities, in this case stored in system RAM.
  • Add CreateRoomView to urls in urls.py of api app.
  • Issue with editing the Room with POST. Result in Bad Request: /api/create-room and "POST /api/create-room HTTP/1.1" 400 12241
  • Solved with ~manage.py migrate --run-syncdb

Tutorial 6 - Material UI Components:

  • Update CreateRoomPage.js with MaterialUI and connect with backend.

Tutorial 7 - Calling APi Endpoints From React

  • Create Room.js, responsible for handling the room page
  • Update HomePage.js with route for room page using roomCode
  • this.props.match.params, match is the prop that stores all the information about how we get to this component from this react router
  • Update urls.py in music_controller\frontend with url for room. Should be able to access http://127.0.0.1:8000/room/ROOMCODE
  • Create a view for the Room. Add view class GetRoom to view.py in the api app Update urls.py in api app with GetRoom view
  • request.GET.get(self.lookup_url_kwarg) will get the code from the parameers in the url that matches the name 'lookup_url_kwarg', in this case 'code'
  • Check to see if works with http://127.0.0.1:8000/api/get-room?code=ROOMCODE
  • Update Room.js with getRoomDetails() to fetch response from api
  • Update fetch in CreateRoomPage.js for the createRoom

Tutorial 8 - Creating the Room Join Page

  • Add center class to index.css. Modify the render div in App.js with className="center"
  • Update RoomJoinPage.js for handling textfieldchange and roombutton pressed
  • Create apiview class JoinRoom to check room exists
  • Add urls to urls.py in api app
  • Update RoomJoinPage.js to handle the api/join-room request

Tutorial 9 - ComponentDidMount and Django Sessions

  • Update HomePage with renderHomePage(). More practice with Grid, Typography and ButtonGroup. disableElevation to remove shadows. variant="contained" to align horizontally
  • Update HomePage path with renderHomePage() function
  • Check if user is already in a room and if they are, we can redirect them to that room
  • Using React lifecycle component methods. Every component in React has a lifecycle which you can monitor and manipulate during 3 phasess: Mounting, Updating and Unmounting
  • Create new apiview class UserInRoom in views.py of api app. Update urls.py with url for UserInRoom
  • Update HomePage with async componentDidMount. On first render, it will show us the homepage, once componentDidMount has finished running it will check if we have a room and redirect if so
  • Update HomePage router in HomePage.js to join session or go to homepage with no room

Tutorial 10 - Django Sessions and Leaving Rooms

  • Update Room.js with MaterialUI
  • Create leaveButtonPressed() in Room.js to connect with backend and access endpoint. Bind this.leaveButtonPressed in the constructor
  • Create new apiview LeaveRoom in views.py of the api app. If host of the room leaves, then removes room and everyone leaves. Query on all the room objects to see if user was the host using Room.objects.filter(host=host_id)
  • Update urls.py with LeaveRoom view
  • Create a clearRoomCode function in HomePage.js to set the state of the roomCode to null.
  • Update '/room/:roomCode' route in HomePage.js with render. props are given by the route. Return a room with the ...props and leaveRoomCallback. '...' is the spread operator, will take all the properties passed in as an object and spread them out as prop1 is prop1 value and prop2 is prop2 value etc. Callback is a way that child component actually modify the parent component. This passes a method to the Room component so the Room component can call that method and modify the Room component
  • Update leaveButtonPressed() in Room.js to call LeaveRoom endpoint with fetch
  • Update getRoomDetails in Room.js to account for if we do not get response.ok. If we don't get response.ok, use method this.props.leaveRoomCallBack(), which was passed to us from the HomePage, clear the state on the HomePage. Then use this.props.history.push("/") to redirect back to the home page because the room doesn't exist

Tutorial 11 - Updating Django Models

  • Create a new Serializer UpdateRoomSerializer to handle the new Room settings update. Cannot pass a unique code to the serializer and therefore need to use code = serializers.CharField(validators=[]) and pass that code to the UpdateRoomSerializer
  • Create a new view class UpdateRoom in views.py of the api app. Will be using patch to update instead of get or post. Import the UpdateRoomSerializer and use that as the serializer_class. Check if room exists and if user is host before sending response
  • Update urls.py in api app for the UpdateRoom view
  • Update this.state in Room.js with showSettings to determine whetehr to show settings or not. Update Room.js with updateShowSettings(value) function. Bind this.updateShowSettings
  • Create renderSettingsButton() in Room.js make a method that will only show settings if the user is the host. Add ternary operator above leave room to show Settings button if user is the host
  • Create renderSettings() to access room settings. Import CreateRoomPage for room settings updates
  • Bind renderSettingsButton and renderSettings to this
  • Add if statement to render show settings or not

Tutorial 12 - React Dafult Props and Callbacks

  • Modify CreateRoomPage so that we can pass props to the CreateRoomPage as well as have default props for a new room. Do so by removing hard-coded default values and providing a defaultProps
  • Create renderCreateButtons() and renderUpdateButtons(). One method that renders the buttons to create a new room and one method that renders the button to update the room.
  • Update render() in CreateRoomPage.js with ternary operator to determine title: "Update Room" or "Create a Room"
  • Create handleUpdateButtonPressed() in CreateRoomPage.js to handle the UpdateRoom on the backend. Add errorMsg and successMsg to the this.state. Bind this.handleUpdateButtonPressed. Update onClick with this.handleUpdateButtonPressed for the renderUpdateButtons()
  • Import Collapse from MaterialUI. Collapse will allow us to show something or collapse it on the screen. Add Collapse to render() function. in is a Boolean value, tells us whether or not to show Collapse or not
  • Update default value in Guest Control of Playback State in CreateRoomPage.js with this.props.guestCanPause.toString()
  • After updating Room in settings, we want the Room page to auto update as well. Update Room.js for the updating CreateRoomPage using updateCallback and calling the this.getRoomDetails. Update handleUpdateButtonPressed() with this.props.updateCallback(); to update Room.js when going back to that Room page. Bind this.getRoomDetails in Room.js
  • ~npm install @material-ui/lab. import Alert from "@material-ui/lab/Alert"; in Create RoomPage.js. Update error or success messages with Alerts. severity: "success" makes it green and error makes it "red". onClose() to clear the successMsg or errorMsg

Tutorial 13 - Spotify API Tutorial (Authentication & Tokens)

  • We authenticate our application with Spotify. Then the user authenticates our application - your application has access to my information it can control the music so on and so forth. Then using that authentication(tokens), we can send requests to the Spotify API and will in turn control the user's music
    • Application requests authorization to access data -> Spotify displays scope of what Application wants to access and prompts user to login -> User logs in and authorizes access
    • Application requests access and refresh tokens -> Spotify returns access and refresh tokens, access tokens used to access info and refresh token used to ask for another token because access tokens expire after a period of time
    • Application uses access tokens in requests to Web API -> Spotify WEB API returns requested data
    • Application - User access token in requests to Web API -> Spotify returns new access token
  • Go to Spotify Developer, log in, and create an app
  • Create new app in music_controller/music_controller using ~python manage.py startapp spotify. Add 'spotify.apps.SpotifyConfig' to INSTALLED_APPS of settings.py in music_controller project
  • Create urls.py and credentials.py in spotify app. Add credentials from Spotify to the credentials.py
  • To authenticate or request access from Spotify - in views.py of spotify app, create apiview class AuthURL(APIView) to generate a url we can use to authenticate our Spotify application. Update urls.py of spotify app for AuthURL. Update urls.py for music-controller project for spotify app
  • Need to set up a redirect URI in credentials.py and in Spotify developer online. After sending request to Authurl, we need a callback or some url that the information requested(returned code and state) gets returned to. After getting Authorization access, we then need to send another request and get the access and refresh token.
  • To create new model that can store tokens, create new model class SpotifyToken in models.py of Spotify app. ~python manage.py makemigrations and ~python manage.py migrate
  • Create util.py in spotify app to save our tokens by saving into a brand new model or updating a model. Create get_user_tokens in util.py to get user tokens. Create update_or_create_user_tokens in util.py to update/create user tokens
  • In views.py of the spotify app, create function spotify_callback. Associate session key/id with their access/refresh tokens and store in our database. Use update_or_create_user_tokens to store tokens in database and then use redirect back to our original application. Update urls.py of spotify app with redirect to frontend for spotify_callback. To allow this, need to add app_name = 'frontend' in urls.py of frontend app because Django needs to know this urls.py file belongs to the frontend app. Need to give the '' path a formal name so that when we call the redirect we know which path we should actually go to
  • Need to check if current user is authenticated. Just need to check if the current session id representing the user is in the database and if the token is expired or not. Create is_spotify_authenticated() and refresh_spotify_token() in utils.py of spotify app
  • Need to set up a view that can tell us whether or not we are authenticated. The util is returning python code but we need it to return json so our front end can understand. Create new apiview class IsAuthenticated() in views.py of spotify app to call util function and return json response. Set up IsAuthenticated in the urls.py of spotify app
  • As soon as we get into a room, if we are the host we need to immediately authenticate our Spotify, in order to control the music. If not the host, they need to authenticate with Spotify -> Show Spotify login prompt, give authorization, take tokens and store them in the database
  • Add spotifyAuthenticated to this.state in Room.js. Create new method authenticateSpotify to send request to backend to check if current user is authenticated, but only if the situation is a host. Need to wait until getRoomDetails() has run before calling authenticateSpotify method and therefore need to modify getRoomDetails() with if statement checking if this.state.isHost then this.authenticateSpotify(). Bind this.authenticateSpotify. This will redirect us to spotify authorization page, then after user authorizes us, it will redirect us spotify callback. The spotify callback will save the token and redirect us to the front end and then the front end will redirect us back to the room page

Tutorial 14 - Using the Spotify API

  • Want to get the information of the currently playing song like the duration, if it's playing or not. Need to send a request to the Spotify API to get the current information about the host of the room's playback information
  • In util.py create function execute_spotify_api_request() to send requests to Spotify
  • To get information about the current song create a new api view class CurrentSong(APIView) in views.py of spotify app. Add path for CurrentSong view in urls.py of spotify app. Test by making a new room, playing Spotify and using http://127.0.0.1:8000/spotify/current-song
  • Update this.state with song as a dictionary with all the song information in Room.js. When song ever changes, this.state.song will be updated accordingly
  • Create new method getCurrentSong() in Room.js to fetch current song data from spotify app. Call getCurrentSong() after authenticating spotify and getting room details with this.getRoomDetails(); in the constructor
  • Need to be constantly checking for updates, like if song is playing or paused. Spotify does not have support for public web sockets, and therefore we need to use polling method. The polling method is basically continually updating every single second. We set up an interval so that every second we update this.state in Room.js. We do this by creating method componentDidMount() to set this.interval and componentWillUnmount() to clear this.interval in Room.js. Also need to bind this.getCurrentSong. Make sure song is playing when checking http://127.0.0.1:8000/room/AOCYOS
  • Create new component MusicPlayer.js to provide a nice music player component in Room.js. In Room.js import MusicPlayer and use to pass song information into MusicPlayer

Tutorial 15 - Pausing & Playing Music with Spotify API

  • Create methods play_song(session_id) and pause_song(session_id) in utils.py of spotify app. Update urls.py with paths for PauseSong and PlaySong
  • Create apiviews def PauseSong() and def PlaySong() in views.py of spotify app
  • Modify MusicPlayer.js to use api views PauseSong and PlaySong
  • Create methods pauseSong and playSong in MusicPlayer.js to fetch url paths. Modify icon buttons to use pauseSong and playSong

Tutorial 16 - Skipping Songs and Handling Votes

  • Create function skip_song(session_id) in util.py of spotify app. Create a path for skip_song in urls.py of spotify app
  • Create new apiview SkipSong in views.py of spotify app
  • Create new method skipSong in MusicPlayer.js
  • To handle votes we will need to store who has voted, check how many votes there are for a room, check if the vote was for which song and current/previous song and when the vote was cast
  • Update Room model in models.py of api app with current_song field
  • Create new model Vote in models.py of spotify app. ForeignKey, we need to pass an instance of another object(in this case Room object) to this Vote model. This will store a reference to the Room object in our Vote. That way whenever we look at a vote we can determine which Room Object that was in, as well as access information about that Room object directly. If Room gets deleted, models.CASCADE will cascade down and delete anything that was referencing this room
  • ~python manage.py makemigrations and ~python manage.py migrate
  • Add method update_room_song() to view CurrentSong(APIView) in views.py in spotify app. from .models import Vote. Add self.update_room_song(room, song_id) before returning Response in view CurrentSong(APIView)
  • Update apiview SkipSong in views.py of spotify app with voting for skipsong. Update CurrentSong method, song['votes'] with votes = len(Vote.objects.filter(room=room, song_id=song_id)) and song['votes_required'] with 'votes_required': room.votes_to_skip
  • Update MusicPlayer.js with {this.props.votes} / {this.props.votes_required} votes needed

Tutorial 17 - Functional Components (useState, useEffect)

  • Create new component Info.js as a functional component
  • Import Info component to HomePage.js. Add button for info and Route path to info in HomePage.js. Update urls.py in frontend with new info path

UpdateBackground

  • Update index.js with gradient specifications
  • Update index.css with gradient
  • Update index.html with gradient div