mirror of
https://github.com/cgzirim/seek-tune.git
synced 2025-12-17 08:54:19 +00:00
Update after multiple changes
This commit is contained in:
parent
9c9156c6a4
commit
e661dc5e2f
8 changed files with 251 additions and 62 deletions
95
client/package-lock.json
generated
95
client/package-lock.json
generated
|
|
@ -21,6 +21,7 @@
|
|||
"react-scripts": "4.0.3",
|
||||
"react-slick": "^0.30.2",
|
||||
"react-toastify": "^8.1.0",
|
||||
"react-youtube": "^10.1.0",
|
||||
"simple-peer": "^9.11.1",
|
||||
"slick-carousel": "^1.8.1",
|
||||
"socket.io-client": "^2.5.0",
|
||||
|
|
@ -14311,6 +14312,11 @@
|
|||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
|
||||
},
|
||||
"node_modules/load-script": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/load-script/-/load-script-1.0.0.tgz",
|
||||
"integrity": "sha512-kPEjMFtZvwL9TaZo0uZ2ml+Ye9HUMmPwbYRJ324qF9tqMejwykJ5ggTyvzmrbBeapCAbk98BSbTeovHEEP1uCA=="
|
||||
},
|
||||
"node_modules/loader-runner": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz",
|
||||
|
|
@ -17974,6 +17980,22 @@
|
|||
"react-dom": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/react-youtube": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-youtube/-/react-youtube-10.1.0.tgz",
|
||||
"integrity": "sha512-ZfGtcVpk0SSZtWCSTYOQKhfx5/1cfyEW1JN/mugGNfAxT3rmVJeMbGpA9+e78yG21ls5nc/5uZJETE3cm3knBg==",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "3.1.3",
|
||||
"prop-types": "15.8.1",
|
||||
"youtube-player": "5.5.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.x"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=0.14.1"
|
||||
}
|
||||
},
|
||||
"node_modules/read-pkg": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
|
||||
|
|
@ -19448,6 +19470,11 @@
|
|||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
|
||||
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
|
||||
},
|
||||
"node_modules/sister": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/sister/-/sister-3.0.2.tgz",
|
||||
"integrity": "sha512-p19rtTs+NksBRKW9qn0UhZ8/TUI9BPw9lmtHny+Y3TinWlOa9jWh9xB0AtPSdmOy49NJJJSSe0Ey4C7h0TrcYA=="
|
||||
},
|
||||
"node_modules/sisteransi": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
|
||||
|
|
@ -23696,6 +23723,29 @@
|
|||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/youtube-player": {
|
||||
"version": "5.5.2",
|
||||
"resolved": "https://registry.npmjs.org/youtube-player/-/youtube-player-5.5.2.tgz",
|
||||
"integrity": "sha512-ZGtsemSpXnDky2AUYWgxjaopgB+shFHgXVpiJFeNB5nWEugpW1KWYDaHKuLqh2b67r24GtP6HoSW5swvf0fFIQ==",
|
||||
"dependencies": {
|
||||
"debug": "^2.6.6",
|
||||
"load-script": "^1.0.0",
|
||||
"sister": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/youtube-player/node_modules/debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"dependencies": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/youtube-player/node_modules/ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
@ -34380,6 +34430,11 @@
|
|||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
|
||||
},
|
||||
"load-script": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/load-script/-/load-script-1.0.0.tgz",
|
||||
"integrity": "sha512-kPEjMFtZvwL9TaZo0uZ2ml+Ye9HUMmPwbYRJ324qF9tqMejwykJ5ggTyvzmrbBeapCAbk98BSbTeovHEEP1uCA=="
|
||||
},
|
||||
"loader-runner": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz",
|
||||
|
|
@ -37318,6 +37373,16 @@
|
|||
"clsx": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"react-youtube": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-youtube/-/react-youtube-10.1.0.tgz",
|
||||
"integrity": "sha512-ZfGtcVpk0SSZtWCSTYOQKhfx5/1cfyEW1JN/mugGNfAxT3rmVJeMbGpA9+e78yG21ls5nc/5uZJETE3cm3knBg==",
|
||||
"requires": {
|
||||
"fast-deep-equal": "3.1.3",
|
||||
"prop-types": "15.8.1",
|
||||
"youtube-player": "5.5.2"
|
||||
}
|
||||
},
|
||||
"read-pkg": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
|
||||
|
|
@ -38455,6 +38520,11 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"sister": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/sister/-/sister-3.0.2.tgz",
|
||||
"integrity": "sha512-p19rtTs+NksBRKW9qn0UhZ8/TUI9BPw9lmtHny+Y3TinWlOa9jWh9xB0AtPSdmOy49NJJJSSe0Ey4C7h0TrcYA=="
|
||||
},
|
||||
"sisteransi": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
|
||||
|
|
@ -41868,6 +41938,31 @@
|
|||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="
|
||||
},
|
||||
"youtube-player": {
|
||||
"version": "5.5.2",
|
||||
"resolved": "https://registry.npmjs.org/youtube-player/-/youtube-player-5.5.2.tgz",
|
||||
"integrity": "sha512-ZGtsemSpXnDky2AUYWgxjaopgB+shFHgXVpiJFeNB5nWEugpW1KWYDaHKuLqh2b67r24GtP6HoSW5swvf0fFIQ==",
|
||||
"requires": {
|
||||
"debug": "^2.6.6",
|
||||
"load-script": "^1.0.0",
|
||||
"sister": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
"react-scripts": "4.0.3",
|
||||
"react-slick": "^0.30.2",
|
||||
"react-toastify": "^8.1.0",
|
||||
"react-youtube": "^10.1.0",
|
||||
"simple-peer": "^9.11.1",
|
||||
"slick-carousel": "^1.8.1",
|
||||
"socket.io-client": "^2.5.0",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import Peer from "simple-peer";
|
||||
import io from "socket.io-client";
|
||||
import Form from "./components/Form";
|
||||
|
|
@ -9,6 +9,8 @@ import { FaMasksTheater, FaMicrophoneLines } from "react-icons/fa6";
|
|||
import { LiaLaptopSolid } from "react-icons/lia";
|
||||
import { ToastContainer, toast, Slide } from "react-toastify";
|
||||
import "react-toastify/dist/ReactToastify.css";
|
||||
import { MediaRecorder, register } from "extendable-media-recorder";
|
||||
import { connect } from "extendable-media-recorder-wav-encoder";
|
||||
|
||||
// const socket = io.connect('http://localhost:5000/');
|
||||
var socket = io("http://localhost:5000/");
|
||||
|
|
@ -23,6 +25,65 @@ function App() {
|
|||
const [peerConnection, setPeerConnection] = useState();
|
||||
const [serverEngaged, setServerEngaged] = useState(false);
|
||||
|
||||
const streamRef = useRef(stream);
|
||||
// const serverEngagedRef = useRef(serverEngaged);
|
||||
const peerConnectionRef = useRef(peerConnection);
|
||||
|
||||
async function record1() {
|
||||
const mediaDevice =
|
||||
audioInput == "device"
|
||||
? navigator.mediaDevices.getDisplayMedia.bind(navigator.mediaDevices)
|
||||
: navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);
|
||||
|
||||
await register(await connect());
|
||||
|
||||
const stream = await mediaDevice({ audio: true });
|
||||
const audioTracks = stream.getAudioTracks();
|
||||
const audioStream = new MediaStream(audioTracks);
|
||||
|
||||
for (const track of stream.getVideoTracks()) {
|
||||
track.stop();
|
||||
}
|
||||
|
||||
const mediaRecorder = new MediaRecorder(audioStream, {
|
||||
mimeType: "audio/wav",
|
||||
});
|
||||
|
||||
const chunks = [];
|
||||
mediaRecorder.ondataavailable = function (e) {
|
||||
chunks.push(e.data);
|
||||
};
|
||||
|
||||
mediaRecorder.addEventListener("stop", () => {
|
||||
const blob = new Blob(chunks, { type: "audio/wav" });
|
||||
const reader = new FileReader();
|
||||
reader.readAsArrayBuffer(blob);
|
||||
|
||||
reader.onload = (event) => {
|
||||
const arrayBuffer = event.target.result;
|
||||
|
||||
var binary = "";
|
||||
var bytes = new Uint8Array(arrayBuffer);
|
||||
var len = bytes.byteLength;
|
||||
for (var i = 0; i < len; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
|
||||
// Convert byte array to base64
|
||||
const base64data = btoa(binary);
|
||||
|
||||
socket.emit("blob", base64data);
|
||||
};
|
||||
});
|
||||
|
||||
mediaRecorder.start();
|
||||
|
||||
// Stop recording after 15 seconds
|
||||
setTimeout(function () {
|
||||
mediaRecorder.stop();
|
||||
}, 15000);
|
||||
}
|
||||
|
||||
function record() {
|
||||
const mediaDevice =
|
||||
audioInput == "device"
|
||||
|
|
@ -90,9 +151,10 @@ function App() {
|
|||
}
|
||||
|
||||
function cleanUp() {
|
||||
if (stream != null) {
|
||||
const currentStream = streamRef.current;
|
||||
if (currentStream) {
|
||||
console.log("Cleaning tracks");
|
||||
stream.getTracks().forEach((track) => track.stop());
|
||||
currentStream.getTracks().forEach((track) => track.stop());
|
||||
}
|
||||
setStream(null);
|
||||
setisListening(false);
|
||||
|
|
@ -108,7 +170,7 @@ function App() {
|
|||
|
||||
// Handle peer events:
|
||||
peer.on("signal", (offerData) => {
|
||||
console.log("Setting Offer!");
|
||||
console.log("Offer generated");
|
||||
setOffer(JSON.stringify(offerData));
|
||||
setPeerConnection(peer);
|
||||
});
|
||||
|
|
@ -124,14 +186,19 @@ function App() {
|
|||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
streamRef.current = stream;
|
||||
peerConnectionRef.current = peerConnection;
|
||||
}, [stream, peerConnection]);
|
||||
|
||||
useEffect(() => {
|
||||
if (offer) {
|
||||
console.log("Offer updated:", offer);
|
||||
console.log("Sending Offer");
|
||||
let offerEncoded = btoa(offer);
|
||||
socket.emit("engage", offerEncoded);
|
||||
|
||||
socket.on("serverEngaged", (answer) => {
|
||||
console.log("ServerSDP: ", answer);
|
||||
console.log("Received answer");
|
||||
let decodedAnswer = atob(answer);
|
||||
if (!serverEngaged && !stream && !peerConnection.destroyed) {
|
||||
peerConnection.signal(decodedAnswer);
|
||||
|
|
@ -145,6 +212,30 @@ function App() {
|
|||
useEffect(() => {
|
||||
socket.on("connect", () => {
|
||||
createPeerConnection();
|
||||
socket.emit("totalSongs", "");
|
||||
});
|
||||
|
||||
// socket.on("serverEngaged", (answer) => {
|
||||
// console.log("Received answer");
|
||||
|
||||
// let decodedAnswer = atob(answer);
|
||||
|
||||
// if (
|
||||
// !serverEngagedRef.current &&
|
||||
// !streamRef.current &&
|
||||
// !peerConnectionRef.current.destroyed
|
||||
// ) {
|
||||
// console.log("Adding answer");
|
||||
// peerConnectionRef.current.signal(decodedAnswer);
|
||||
// }
|
||||
|
||||
// console.log("Engaged Server");
|
||||
// setServerEngaged(true);
|
||||
// });
|
||||
|
||||
socket.on("failedToEngage", () => {
|
||||
console.log("Server failed to engage");
|
||||
stopListening();
|
||||
});
|
||||
|
||||
socket.on("matches", (matches) => {
|
||||
|
|
@ -153,6 +244,7 @@ function App() {
|
|||
setMatches(matches);
|
||||
console.log("Matches: ", matches);
|
||||
} else {
|
||||
toast("No song found.");
|
||||
console.log("No Matches");
|
||||
}
|
||||
|
||||
|
|
@ -189,7 +281,7 @@ function App() {
|
|||
function stopListening() {
|
||||
console.log("Pause Clicked");
|
||||
cleanUp();
|
||||
peerConnection.destroy();
|
||||
peerConnectionRef.current.destroy();
|
||||
|
||||
setTimeout(() => {
|
||||
createPeerConnection();
|
||||
|
|
@ -209,7 +301,6 @@ function App() {
|
|||
setisListening(true);
|
||||
|
||||
setStream(stream);
|
||||
stream.getVideoTracks()[0].onended = stopListening;
|
||||
stream.getAudioTracks()[0].onended = stopListening;
|
||||
})
|
||||
.catch((error) => {
|
||||
|
|
@ -243,7 +334,7 @@ function App() {
|
|||
<Listen
|
||||
stopListening={stopListening}
|
||||
disable={!serverEngaged}
|
||||
startListening={startListening}
|
||||
startListening={record1}
|
||||
isListening={isListening}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,33 @@
|
|||
import React from "react";
|
||||
import React, { useRef } from "react";
|
||||
import YouTube from "react-youtube";
|
||||
import styles from "./styles/CarouselSliders.module.css";
|
||||
|
||||
const CarouselSliders = (props) => {
|
||||
const [activeIdx, setActiveIdx] = React.useState(0);
|
||||
const players = useRef({});
|
||||
|
||||
const opts = {
|
||||
// width: "420",
|
||||
// height: "210",
|
||||
};
|
||||
|
||||
const onReady = (event, videoId) => {
|
||||
players.current[videoId] = event.target;
|
||||
};
|
||||
|
||||
const onPlay = (event) => {
|
||||
const videoId = event.target.getVideoData().video_id;
|
||||
// Pause other videos
|
||||
Object.values(players.current).forEach((player) => {
|
||||
const otherVideoId = player.getVideoData().video_id;
|
||||
if (
|
||||
otherVideoId !== videoId &&
|
||||
player.getPlayerState() === 1 /* Playing */
|
||||
) {
|
||||
player.pauseVideo();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -15,22 +40,22 @@ const CarouselSliders = (props) => {
|
|||
parseInt(h, 10) * 360 + parseInt(m, 10) * 60 + parseInt(s, 10);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
key={index}
|
||||
id={`slide-${index}`}
|
||||
className={styles.SlideItem}
|
||||
>
|
||||
<iframe
|
||||
className="iframe-youtube"
|
||||
src={`https://www.youtube.com/embed/${match.youtubeid}?start=${timestamp}`}
|
||||
title={match.songname}
|
||||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
</div>
|
||||
</>
|
||||
<div
|
||||
key={index}
|
||||
id={`slide-${index}`}
|
||||
className={styles.SlideItem}
|
||||
>
|
||||
<YouTube
|
||||
videoId={match.youtubeid}
|
||||
opts={{
|
||||
...opts,
|
||||
playerVars: { start: timestamp, rel: 0 },
|
||||
}}
|
||||
iframeClassName={styles.Iframe}
|
||||
onReady={(event) => onReady(event, match.youtubeid)}
|
||||
onPlay={onPlay}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ const Form = ({ socket }) => {
|
|||
return (
|
||||
<form className={styles.Form}>
|
||||
<div style={{ flexGrow: 1 }}>
|
||||
<div>Download songs to the server</div>
|
||||
<div>Add songs to the server</div>
|
||||
<input
|
||||
type="text"
|
||||
name="spotifyUrl"
|
||||
|
|
|
|||
|
|
@ -32,9 +32,8 @@
|
|||
}
|
||||
|
||||
.SlideItem {
|
||||
/* height: 100%;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
min-width: 90%; */
|
||||
margin-left: 20px;
|
||||
flex-grow: 1;
|
||||
scroll-snap-align: center;
|
||||
|
|
@ -44,13 +43,13 @@
|
|||
.SlideItem:nth-child(2) { background-color: dodgerblue;}
|
||||
.SlideItem:nth-child(3) { background-color: greenyellow;}
|
||||
|
||||
.iframe-youtube {
|
||||
width: 420px;
|
||||
height: 210px;
|
||||
.Iframe {
|
||||
width: 500px;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
.iframe-youtube {
|
||||
.Iframe {
|
||||
width: 420px;
|
||||
height: 200px;
|
||||
}
|
||||
|
|
@ -79,15 +78,18 @@
|
|||
}
|
||||
}
|
||||
@media screen and (max-width: 600px) {
|
||||
.iframe-youtube {
|
||||
.Iframe {
|
||||
width: 820px;
|
||||
height: 400px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 700px) {
|
||||
.iframe-youtube {
|
||||
.Iframe {
|
||||
width: 420px;
|
||||
height: 210px;
|
||||
}
|
||||
/* .SlideItem {
|
||||
width: 20;
|
||||
} */
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
Form {
|
||||
width: 80%;
|
||||
margin: auto;
|
||||
margin-bottom: 30px;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: end;
|
||||
|
|
@ -17,30 +18,3 @@ Form {
|
|||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.FormStatus {
|
||||
margin-bottom: 2px;
|
||||
padding: 2px 5px;
|
||||
background-color: #3498db;
|
||||
border-radius: 2px;
|
||||
animation: slideUp 0.8s forwards;
|
||||
}
|
||||
|
||||
.FormStatus.NoMessage {
|
||||
/* animation: slideDown 0.8s forwards; */
|
||||
transform: translateX(-50%) translateY(100%);
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
transform: translateY(100%);
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* .slideDown {
|
||||
transform: translateX(-50%) translateY(100%);
|
||||
} */
|
||||
|
|
@ -50,6 +50,7 @@ main {
|
|||
.App {
|
||||
width: 65%;
|
||||
margin: auto;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.songs {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue