player
This commit is contained in:
parent
9f14bc261a
commit
28b8d4ad84
10 changed files with 143 additions and 106 deletions
25
app/app.css
25
app/app.css
|
@ -4,26 +4,33 @@ body {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
header nav ul {
|
main {
|
||||||
|
flex-grow: 1;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
nav ul {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
header nav ul li {
|
nav ul li {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
line-height: 40px;
|
line-height: 40px;
|
||||||
}
|
}
|
||||||
header nav ul li:hover {
|
nav ul li:hover {
|
||||||
background-color: red;
|
background-color: red;
|
||||||
}
|
}
|
||||||
main {
|
#player:not([data-state=play]) .pause {
|
||||||
flex-grow: 1;
|
display: none;
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
}
|
||||||
footer {
|
#player[data-state=play] .play {
|
||||||
flex-basis: 40px;
|
display: none;
|
||||||
|
}
|
||||||
|
#player:not(.random) .random,
|
||||||
|
#player:not(.repeat) .repeat {
|
||||||
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,6 @@ body {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
@import "header.less";
|
|
||||||
@import "main.less";
|
@import "main.less";
|
||||||
@import "footer.less";
|
@import "nav.less";
|
||||||
|
@import "player.less";
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
footer {
|
|
||||||
flex-basis: 40px;
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
header {
|
|
||||||
nav ul {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
list-style: none;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
|
|
||||||
li {
|
|
||||||
text-align: center;
|
|
||||||
flex-grow: 1;
|
|
||||||
line-height: 40px;
|
|
||||||
|
|
||||||
&:hover { background-color:red;}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
15
app/css/nav.less
Normal file
15
app/css/nav.less
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
nav ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
li {
|
||||||
|
text-align: center;
|
||||||
|
flex-grow: 1;
|
||||||
|
line-height: 40px;
|
||||||
|
|
||||||
|
&:hover { background-color:red;}
|
||||||
|
}
|
||||||
|
}
|
6
app/css/player.less
Normal file
6
app/css/player.less
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
#player {
|
||||||
|
&:not([data-state=play]) .pause { display: none; }
|
||||||
|
&[data-state=play] .play { display: none; }
|
||||||
|
|
||||||
|
&:not(.random) .random, &:not(.repeat) .repeat { opacity: 0.5; }
|
||||||
|
}
|
|
@ -8,14 +8,19 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<nav>
|
<section id="player">
|
||||||
<ul>
|
<span class="art"></span>
|
||||||
<li>Q</li>
|
<span class="title"></span>
|
||||||
<li>Playlists</li>
|
<span class="artist"></span>
|
||||||
<li>Library</li>
|
<span class="album"></span>
|
||||||
<li>Misc</li>
|
<span class="elapsed"></span>/<span class="duration"></span>
|
||||||
</ul>
|
<button class="play">⏯️▶</button>
|
||||||
</nav>
|
<button class="pause">⏸️</button>
|
||||||
|
<button class="prev">⏮ ⏮️</button>
|
||||||
|
<button class="next">⏭️</button>
|
||||||
|
<button class="repeat">🔁</button>
|
||||||
|
<button class="random">🔀</button>
|
||||||
|
</section>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
main<br/>
|
main<br/>
|
||||||
|
@ -31,19 +36,14 @@
|
||||||
main<br/>
|
main<br/>
|
||||||
</main>
|
</main>
|
||||||
<footer>
|
<footer>
|
||||||
<span class="art"></span>
|
<nav>
|
||||||
<span class="title"></span>
|
<ul>
|
||||||
<span class="artist"></span>
|
<li>Q</li>
|
||||||
<span class="album"></span>
|
<li>Playlists</li>
|
||||||
<span class="time1"></span>/<span class="time2"></span>
|
<li>Library</li>
|
||||||
<span class="status">
|
<li>Misc</li>
|
||||||
<button class="play">play</button>
|
</ul>
|
||||||
<button class="pause">pause</button>
|
</nav>
|
||||||
</span>
|
|
||||||
<button class="prev">prev</button>
|
|
||||||
<button class="next">next</button>
|
|
||||||
<button class="repeat">repeat</button>
|
|
||||||
<button class="random">random</button>
|
|
||||||
</footer>
|
</footer>
|
||||||
<script type="module" src="js/app.js"></script>
|
<script type="module" src="js/app.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import * as mpd from "./mpd.js";
|
import * as mpd from "./mpd.js";
|
||||||
import * as status from "./status.js";
|
import * as player from "./player.js";
|
||||||
import * as art from "./art.js";
|
import * as art from "./art.js";
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
await mpd.init();
|
await mpd.init();
|
||||||
status.init();
|
player.init();
|
||||||
window.mpd = mpd;
|
window.mpd = mpd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
81
app/js/player.js
Normal file
81
app/js/player.js
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
import * as mpd from "./mpd.js";
|
||||||
|
import * as art from "./art.js";
|
||||||
|
|
||||||
|
const DELAY = 2000;
|
||||||
|
const DOM = {};
|
||||||
|
|
||||||
|
let current = {};
|
||||||
|
let node;
|
||||||
|
let idleTimeout = null;
|
||||||
|
|
||||||
|
function formatTime(sec) {
|
||||||
|
sec = Math.round(sec);
|
||||||
|
let m = Math.floor(sec / 60);
|
||||||
|
let s = sec % 60;
|
||||||
|
return `${m}:${s.toString().padStart(2, "0")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function update(data) {
|
||||||
|
DOM.elapsed.textContent = formatTime(Number(data["elapsed"] || 0)); // changed time
|
||||||
|
|
||||||
|
if (data["file"] != current["file"]) { // changed song
|
||||||
|
DOM.duration.textContent = formatTime(Number(data["duration"] || 0));
|
||||||
|
DOM.title.textContent = data["Title"] || "";
|
||||||
|
DOM.album.textContent = data["Album"] || "";
|
||||||
|
DOM.artist.textContent = data["Artist"] || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data["Artist"] != current["Artist"] || data["Album"] != current["Album"]) { // changed album (art)
|
||||||
|
DOM.art.innerHTML = "";
|
||||||
|
art.get(data["Artist"], data["Album"], data["file"]).then(src => {
|
||||||
|
if (!src) { return; }
|
||||||
|
let image = document.createElement("img");
|
||||||
|
image.src = src;
|
||||||
|
DOM.art.appendChild(image);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
node.classList.toggle("random", data["random"] == "1");
|
||||||
|
node.classList.toggle("repeat", data["repeat"] == "1");
|
||||||
|
node.dataset.state = data["state"];
|
||||||
|
|
||||||
|
current = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sync() {
|
||||||
|
let data = await mpd.status();
|
||||||
|
update(data);
|
||||||
|
idle();
|
||||||
|
}
|
||||||
|
|
||||||
|
function idle() {
|
||||||
|
idleTimeout = setTimeout(sync, DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearIdle() {
|
||||||
|
idleTimeout && clearTimeout(idleTimeout);
|
||||||
|
idleTimeout = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function command(cmd) {
|
||||||
|
clearIdle();
|
||||||
|
let data = await mpd.commandAndStatus(cmd);
|
||||||
|
update(data);
|
||||||
|
idle();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function init() {
|
||||||
|
node = document.querySelector("#player");
|
||||||
|
let all = node.querySelectorAll("[class]");
|
||||||
|
Array.from(all).forEach(node => DOM[node.className] = node);
|
||||||
|
|
||||||
|
DOM.play.addEventListener("click", e => command("play"));
|
||||||
|
DOM.pause.addEventListener("click", e => command("pause 1"));
|
||||||
|
DOM.prev.addEventListener("click", e => command("previous"));
|
||||||
|
DOM.next.addEventListener("click", e => command("next"));
|
||||||
|
|
||||||
|
DOM.random.addEventListener("click", e => command(`random ${current["random"] == "1" ? "0" : "1"}`));
|
||||||
|
DOM.repeat.addEventListener("click", e => command(`repeat ${current["repeat"] == "1" ? "0" : "1"}`));
|
||||||
|
|
||||||
|
sync();
|
||||||
|
}
|
|
@ -1,52 +0,0 @@
|
||||||
import * as mpd from "./mpd.js";
|
|
||||||
import * as art from "./art.js";
|
|
||||||
|
|
||||||
const DELAY = 2000;
|
|
||||||
const DOM = {};
|
|
||||||
|
|
||||||
let current = {};
|
|
||||||
|
|
||||||
async function tick() {
|
|
||||||
let data = await mpd.status();
|
|
||||||
|
|
||||||
update(data);
|
|
||||||
|
|
||||||
// console.log(data);
|
|
||||||
setTimeout(tick, DELAY);
|
|
||||||
}
|
|
||||||
|
|
||||||
function update(data) {
|
|
||||||
if (data["Title"] != current["Title"]) { DOM.title.textContent = data["Title"]; }
|
|
||||||
|
|
||||||
if (data["Artist"] != current["Artist"] || data["Album"] != current["Album"]) {
|
|
||||||
DOM.art.innerHTML = "";
|
|
||||||
art.get(data["Artist"], data["Album"], data["file"]).then(src => {
|
|
||||||
if (!src) { return; }
|
|
||||||
let image = document.createElement("img");
|
|
||||||
image.src = src;
|
|
||||||
DOM.art.appendChild(image);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
current = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function play() {
|
|
||||||
let data = await mpd.commandAndStatus("pause 0");
|
|
||||||
update(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function pause() {
|
|
||||||
let data = await mpd.commandAndStatus("pause 1");
|
|
||||||
update(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function init() {
|
|
||||||
let all = document.querySelectorAll("footer [class]");
|
|
||||||
Array.from(all).forEach(node => DOM[node.className] = node);
|
|
||||||
|
|
||||||
DOM.play.addEventListener("click", e => play());
|
|
||||||
DOM.pause.addEventListener("click", e => pause());
|
|
||||||
|
|
||||||
tick();
|
|
||||||
}
|
|
Loading…
Reference in a new issue