Initial Commit
TODO: Keyboard Navigation
This commit is contained in:
7
README.md
Normal file
7
README.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
### Retrowave Player
|
||||||
|
|
||||||
|
A Minimalist Retrowave music player for the web (frontend only).
|
||||||
|
|
||||||
|
> This was a challenge to write a minimalist music player using Howler.js in 2 hours limitation was to use only vanilla JS.
|
||||||
|
|
||||||
|
Uses: https://retrowave.ru/
|
||||||
182
hg.css
Normal file
182
hg.css
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
#app,
|
||||||
|
body,
|
||||||
|
html {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
background: #FAFAFA;
|
||||||
|
font-family: 'Helvetica Neue', arial, sans-serif;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #444;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: #f5b7f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.music-player {
|
||||||
|
width: 350px;
|
||||||
|
height: 500px;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
margin: -250px 0 0 -175px;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 3px 3px 20px rgba(0, 0, 0, 0.5);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.music-player .color-overlay {
|
||||||
|
/* Rectangle 11: */
|
||||||
|
background: rgba(84, 104, 110, 0.4);
|
||||||
|
width: 350px;
|
||||||
|
height: 500px;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 10;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
transition: background 0.3s cubic-bezier(0.33, 0.66, 0.66, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.music-player .gradient-overlay {
|
||||||
|
/* bg-gradient: */
|
||||||
|
background-image: -webkit-linear-gradient(rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.6) 21%);
|
||||||
|
background-image: -moz-linear-gradient(rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.6) 21%);
|
||||||
|
background-image: -o-linear-gradient(rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.6) 21%);
|
||||||
|
background-image: linear-gradient(rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.6) 21%);
|
||||||
|
width: 350px;
|
||||||
|
height: 500px;
|
||||||
|
position: absolute;
|
||||||
|
top: 350px;
|
||||||
|
left: 0;
|
||||||
|
z-index: 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-controls {
|
||||||
|
/* width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 100px;
|
||||||
|
left: 0;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 50px;
|
||||||
|
color: #DCE3E7;
|
||||||
|
font-family: 'Droid Serif', serif;
|
||||||
|
font-style: 16px;
|
||||||
|
line-height: 24px;
|
||||||
|
z-index: 20;
|
||||||
|
opacity: 0;
|
||||||
|
transition: bottom 0.3s, opacity 0.3s cubic-bezier(0.33, 0.66, 0.66, 1); */
|
||||||
|
}
|
||||||
|
|
||||||
|
.music-player:hover .player-controls {
|
||||||
|
opacity: 1;
|
||||||
|
bottom: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.music-player:hover .color-overlay {
|
||||||
|
background: rgba(84, 104, 110, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-content {
|
||||||
|
text-align: center;
|
||||||
|
margin: 130px 0 0 0;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 20;
|
||||||
|
width: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
color: #9CC9E3;
|
||||||
|
font-family: 'Roboto', sans-serif;
|
||||||
|
margin-bottom: 0;
|
||||||
|
background: rgb(0 0 0 / 55%);
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
width: 50px;
|
||||||
|
height: 3px;
|
||||||
|
margin: 20px auto;
|
||||||
|
border: 0;
|
||||||
|
background: #D0BB57;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro {
|
||||||
|
width: 170px;
|
||||||
|
margin: 0 auto;
|
||||||
|
color: #DCE3E7;
|
||||||
|
font-family: 'Droid Serif', serif;
|
||||||
|
font-size: 13px;
|
||||||
|
font-style: italic;
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#info {
|
||||||
|
bottom: 0px;
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
z-index: 999;
|
||||||
|
color: #ffffff;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#history {
|
||||||
|
bottom: 0px;
|
||||||
|
position: absolute;
|
||||||
|
left: 8px;
|
||||||
|
z-index: 999;
|
||||||
|
color: #ffffff;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog {
|
||||||
|
text-align: center;
|
||||||
|
padding: 50px;
|
||||||
|
z-index: 999;
|
||||||
|
border: 1px solid #000000;
|
||||||
|
margin-left: 15px;
|
||||||
|
width: 50%;
|
||||||
|
background: #000000;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog button {
|
||||||
|
background: #ffffff;
|
||||||
|
border: none;
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog::backdrop {
|
||||||
|
background: repeating-linear-gradient(45deg,
|
||||||
|
rgba(0, 0, 0, 0.2),
|
||||||
|
rgba(0, 0, 0, 0.2) 1px,
|
||||||
|
rgba(0, 0, 0, 0.3) 1px,
|
||||||
|
rgba(0, 0, 0, 0.3) 20px);
|
||||||
|
backdrop-filter: blur(3px);
|
||||||
|
}
|
||||||
37
index.html
Normal file
37
index.html
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Retrowave Player</title>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/howler/2.2.3/howler.min.js"></script>
|
||||||
|
<link rel="stylesheet" href="hg.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<dialog id="dialog">
|
||||||
|
<p>Welcome to Retrowave Player</p>
|
||||||
|
<form method="dialog">
|
||||||
|
<button id="initButton">OK</button>
|
||||||
|
</form>
|
||||||
|
</dialog>
|
||||||
|
<div id="app">
|
||||||
|
<div class="music-player"
|
||||||
|
style="background-image: url("http://retrowave.ru/artwork/ffd65c85c036971075677e76e6b250eef4c6ef2e.jpg"); background-position: 50% 50%; background-repeat: no-repeat;">
|
||||||
|
<div class="title-content">
|
||||||
|
<div class="intro">Now Playing</div>
|
||||||
|
<hr>
|
||||||
|
<h3 id="track-name">Retrowave Player</h3>
|
||||||
|
</div>
|
||||||
|
<div class="gradient-overlay"></div>
|
||||||
|
<div class="color-overlay"></div>
|
||||||
|
<div id="history">history</div>
|
||||||
|
<div id="info">info</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="player.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
145
player.js
Normal file
145
player.js
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
(function () {
|
||||||
|
openDialog();
|
||||||
|
let isPlaying = false;
|
||||||
|
let currentTracks = [];
|
||||||
|
let cursor = 0;
|
||||||
|
let howerInstance;
|
||||||
|
const retroWaveRu = "https://retrowave.ru";
|
||||||
|
getMusic();
|
||||||
|
|
||||||
|
let titleEl = document.getElementById("track-name");
|
||||||
|
let coverArtEl = document.getElementsByClassName("music-player")[0];
|
||||||
|
|
||||||
|
coverArtEl.addEventListener("click", () => {
|
||||||
|
if (howerInstance) {
|
||||||
|
isPlaying = !isPlaying;
|
||||||
|
|
||||||
|
if (isPlaying) {
|
||||||
|
howerInstance.play();
|
||||||
|
} else {
|
||||||
|
howerInstance.pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("initButton").addEventListener("click", () => {
|
||||||
|
playMusic();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("history").addEventListener("click", () => {
|
||||||
|
downloadHistory();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("keyup", (event) => {
|
||||||
|
const { key } = event;
|
||||||
|
switch (key) {
|
||||||
|
case "Space":
|
||||||
|
break;
|
||||||
|
case "ArrowLeft":
|
||||||
|
break;
|
||||||
|
case "ArrowRight":
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let touchstartX = 0;
|
||||||
|
let touchendX = 0;
|
||||||
|
const musixPlayerEl = document.getElementsByClassName("music-player")[0];
|
||||||
|
if (musixPlayerEl) {
|
||||||
|
musixPlayerEl.addEventListener("touchstart", (e) => {
|
||||||
|
touchstartX = e.changedTouches[0].screenX;
|
||||||
|
});
|
||||||
|
|
||||||
|
musixPlayerEl.addEventListener("touchend", (e) => {
|
||||||
|
touchendX = e.changedTouches[0].screenX;
|
||||||
|
checkDirection();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkDirection() {
|
||||||
|
if (touchendX < touchstartX) {
|
||||||
|
// Swipe Left
|
||||||
|
}
|
||||||
|
if (touchendX > touchstartX) {
|
||||||
|
// Swipe Right
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openDialog() {
|
||||||
|
const dialog = document.getElementById("dialog");
|
||||||
|
dialog.showModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMusic() {
|
||||||
|
fetch(`https://retrowave.ru/api/v1/tracks?limit=5&cursor=${cursor}`)
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((res) => {
|
||||||
|
const {
|
||||||
|
body: { tracks, cursor: currentCursor },
|
||||||
|
} = res;
|
||||||
|
cursor = currentCursor;
|
||||||
|
currentTracks = tracks;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function initPlayer() { }
|
||||||
|
|
||||||
|
function playMusic() {
|
||||||
|
const currentTrack = currentTracks[0];
|
||||||
|
const singleTrack = currentTrack.streamUrl;
|
||||||
|
const fullTrack = `${retroWaveRu}${singleTrack}`;
|
||||||
|
howerInstance = new Howl({
|
||||||
|
src: [fullTrack],
|
||||||
|
html5: true,
|
||||||
|
onend: function () {
|
||||||
|
currentTracks.shift();
|
||||||
|
if (currentTracks.length <= 3) {
|
||||||
|
getMusic();
|
||||||
|
}
|
||||||
|
playMusic();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
isPlaying = true;
|
||||||
|
|
||||||
|
updateInfo(currentTrack);
|
||||||
|
|
||||||
|
addToHistory(currentTrack);
|
||||||
|
howerInstance.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateInfo(trackDetails) {
|
||||||
|
titleEl.innerText = trackDetails.title;
|
||||||
|
coverArtEl.style.backgroundImage = `url("${retroWaveRu}${trackDetails.artworkUrl}")`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHistory() {
|
||||||
|
let localHistoryStore = localStorage.getItem("retrowave-history") || "[]";
|
||||||
|
let historyArray = JSON.parse(localHistoryStore);
|
||||||
|
console.log(historyArray);
|
||||||
|
return historyArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addToHistory(trackDetails) {
|
||||||
|
let historyArray = getHistory();
|
||||||
|
historyArray.push(trackDetails);
|
||||||
|
localStorage.setItem("retrowave-history", JSON.stringify(historyArray));
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadHistory() {
|
||||||
|
const historyArray = getHistory();
|
||||||
|
let element = document.createElement("a");
|
||||||
|
element.setAttribute(
|
||||||
|
"href",
|
||||||
|
"data:application/json;charset=utf-8," +
|
||||||
|
encodeURIComponent(JSON.stringify(historyArray))
|
||||||
|
);
|
||||||
|
element.setAttribute("download", "history.json");
|
||||||
|
|
||||||
|
element.style.display = "none";
|
||||||
|
document.body.appendChild(element);
|
||||||
|
|
||||||
|
element.click();
|
||||||
|
|
||||||
|
document.body.removeChild(element);
|
||||||
|
}
|
||||||
|
})();
|
||||||
Reference in New Issue
Block a user