player: progress, meter
This commit is contained in:
parent
e75ce14ec0
commit
ed1eeadad5
9 changed files with 120 additions and 44 deletions
47
app/app.css
47
app/app.css
|
@ -159,17 +159,52 @@ nav ul li.active {
|
|||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
#player progress {
|
||||
width: 100%;
|
||||
}
|
||||
#player .controls {
|
||||
#player .timeline {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
#player .controls .icon {
|
||||
#player .timeline .duration,
|
||||
#player .timeline .elapsed {
|
||||
flex-basis: 5ch;
|
||||
}
|
||||
#player .timeline .duration {
|
||||
text-align: right;
|
||||
}
|
||||
#player .timeline progress {
|
||||
flex-grow: 1;
|
||||
height: 8px;
|
||||
}
|
||||
#player .controls {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-basis: 160px;
|
||||
}
|
||||
#player .controls .playback {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
}
|
||||
#player .controls .playback .icon {
|
||||
width: 32px;
|
||||
margin: 8px;
|
||||
}
|
||||
#player .controls .playback .icon-play,
|
||||
#player .controls .playback .icon-pause {
|
||||
width: 48px;
|
||||
}
|
||||
#player .controls .volume {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
#player .controls .volume .icon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
#player .controls .volume meter {
|
||||
flex-grow: 1;
|
||||
vertical-align: top;
|
||||
height: 8px;
|
||||
}
|
||||
#player .misc {
|
||||
display: flex;
|
||||
|
|
|
@ -29,17 +29,53 @@
|
|||
|
||||
.title, .subtitle { .long-line; }
|
||||
|
||||
.timeline {
|
||||
.flex-row;
|
||||
|
||||
progress {
|
||||
width: 100%;
|
||||
.duration, .elapsed {
|
||||
flex-basis: 5ch;
|
||||
}
|
||||
|
||||
.duration { text-align: right; }
|
||||
|
||||
progress {
|
||||
flex-grow: 1;
|
||||
height: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.controls {
|
||||
.flex-row;
|
||||
.icon {
|
||||
width: 32px;
|
||||
margin: 8px;
|
||||
.flex-column;
|
||||
flex-basis: 64px + 2*48px;
|
||||
|
||||
.playback {
|
||||
.flex-row;
|
||||
justify-content: space-around;
|
||||
|
||||
.icon {
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
.icon-play, .icon-pause {
|
||||
width: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.volume {
|
||||
.flex-row;
|
||||
|
||||
.icon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
meter {
|
||||
flex-grow: 1;
|
||||
vertical-align: top;
|
||||
height: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
.misc {
|
||||
|
|
1
app/icons/volume-high.svg
Normal file
1
app/icons/volume-high.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M14,3.23V5.29C16.89,6.15 19,8.83 19,12C19,15.17 16.89,17.84 14,18.7V20.77C18,19.86 21,16.28 21,12C21,7.72 18,4.14 14,3.23M16.5,12C16.5,10.23 15.5,8.71 14,7.97V16C15.5,15.29 16.5,13.76 16.5,12M3,9V15H7L12,20V4L7,9H3Z" /></svg>
|
After Width: | Height: | Size: 510 B |
|
@ -13,13 +13,22 @@
|
|||
<div class="info">
|
||||
<h2 class="title"></h2>
|
||||
<div class="subtitle"></div>
|
||||
<progress class="elapsed"></progress>
|
||||
<div class="timeline">
|
||||
<span class="elapsed"></span>
|
||||
<progress></progress>
|
||||
<span class="duration"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<button class="prev" data-icon="rewind"></button>
|
||||
<button class="play" data-icon="play"></button>
|
||||
<button class="pause" data-icon="pause"></button>
|
||||
<button class="next" data-icon="fast-forward"></button>
|
||||
<div class="playback">
|
||||
<button class="prev" data-icon="rewind"></button>
|
||||
<button class="play" data-icon="play"></button>
|
||||
<button class="pause" data-icon="pause"></button>
|
||||
<button class="next" data-icon="fast-forward"></button>
|
||||
</div>
|
||||
<div class="volume">
|
||||
<meter max="100"></meter>
|
||||
</div>
|
||||
</div>
|
||||
<div class="misc">
|
||||
<button class="repeat" data-icon="repeat"></button>
|
||||
|
@ -52,10 +61,6 @@
|
|||
</section>
|
||||
<section id="settings">
|
||||
<dl>
|
||||
<dt>Volume</dt>
|
||||
<dd>
|
||||
<input type="range" name="volume" min="0" max="100" step="1" />
|
||||
</dd>
|
||||
<dt>Theme</dt>
|
||||
<dd>
|
||||
<select name="theme">
|
||||
|
|
|
@ -7,10 +7,10 @@ export function time(sec) {
|
|||
return `${m}:${s.toString().padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
export function subtitle(data) {
|
||||
export function subtitle(data, options = {duration:true}) {
|
||||
let tokens = [];
|
||||
data["Artist"] && tokens.push(data["Artist"]);
|
||||
data["Album"] && tokens.push(data["Album"]);
|
||||
data["duration"] && tokens.push(time(Number(data["duration"])));
|
||||
options.duration && data["duration"] && tokens.push(time(Number(data["duration"])));
|
||||
return tokens.join(SEPARATOR);
|
||||
}
|
|
@ -29,6 +29,9 @@ ICONS["close-circle"] = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="ht
|
|||
ICONS["minus"] = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 24 24">
|
||||
<path d="M19,13H5V11H19V13Z"/>
|
||||
</svg>`;
|
||||
ICONS["volume-high"] = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 24 24">
|
||||
<path d="M14,3.23V5.29C16.89,6.15 19,8.83 19,12C19,15.17 16.89,17.84 14,18.7V20.77C18,19.86 21,16.28 21,12C21,7.72 18,4.14 14,3.23M16.5,12C16.5,10.23 15.5,8.71 14,7.97V16C15.5,15.29 16.5,13.76 16.5,12M3,9V15H7L12,20V4L7,9H3Z"/>
|
||||
</svg>`;
|
||||
ICONS["shuffle"] = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 24 24">
|
||||
<path d="M14.83,13.41L13.42,14.82L16.55,17.95L14.5,20H20V14.5L17.96,16.54L14.83,13.41M14.5,4L16.54,6.04L4,18.59L5.41,20L17.96,7.46L20,9.5V4M10.59,9.17L5.41,4L4,5.41L9.17,10.58L10.59,9.17Z"/>
|
||||
</svg>`;
|
||||
|
|
|
@ -63,16 +63,14 @@ export async function command(cmd) {
|
|||
export async function commandAndStatus(cmd) {
|
||||
let lines = await command([cmd, "status", "currentsong"]);
|
||||
let status = parser.linesToStruct(lines);
|
||||
// duration returned 2x => arrayfied
|
||||
if ("duration" in status) { status["duration"] = status["duration"][0]; }
|
||||
if (status["duration"] instanceof Array) { status["duration"] = status["duration"][0]; }
|
||||
return status;
|
||||
}
|
||||
|
||||
export async function status() {
|
||||
let lines = await command(["status", "currentsong"]);
|
||||
let status = parser.linesToStruct(lines);
|
||||
// duration returned 2x => arrayfied
|
||||
if ("duration" in status) { status["duration"] = status["duration"][0]; }
|
||||
if (status["duration"] instanceof Array) { status["duration"] = status["duration"][0]; }
|
||||
return status;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ import * as art from "./lib/art.js";
|
|||
import * as html from "./lib/html.js";
|
||||
import * as format from "./lib/format.js";
|
||||
import * as pubsub from "./lib/pubsub.js";
|
||||
import * as settings from "./settings.js";
|
||||
|
||||
const DELAY = 2000;
|
||||
const DOM = {};
|
||||
|
@ -13,15 +12,20 @@ let node;
|
|||
let idleTimeout = null;
|
||||
|
||||
function sync(data) {
|
||||
settings.notifyVolume(data["volume"]);
|
||||
if ("volume" in data) { DOM.volume.value = data["volume"]; }
|
||||
|
||||
DOM.elapsed.value = Number(data["elapsed"] || 0); // changed time
|
||||
// changed time
|
||||
let elapsed = Number(data["elapsed"] || 0);
|
||||
DOM.progress.value = elapsed;
|
||||
DOM.elapsed.textContent = format.time(elapsed);
|
||||
|
||||
if (data["file"] != current["file"]) { // changed song
|
||||
if (data["file"]) { // playing at all?
|
||||
DOM.elapsed.max = Number(data["duration"]);
|
||||
let duration = Number(data["duration"]);
|
||||
DOM.duration.textContent = format.time(duration);
|
||||
DOM.progress.max = duration;
|
||||
DOM.title.textContent = data["Title"] || data["file"].split("/").pop();
|
||||
DOM.subtitle.textContent = format.subtitle(data);
|
||||
DOM.subtitle.textContent = format.subtitle(data, {duration:false});
|
||||
} else {
|
||||
DOM.title.textContent = "";
|
||||
DOM.subtitle.textContent = "";
|
||||
|
@ -79,6 +83,11 @@ export function init(n) {
|
|||
let all = node.querySelectorAll("[class]");
|
||||
Array.from(all).forEach(node => DOM[node.className] = node);
|
||||
|
||||
DOM.progress = DOM.timeline.querySelector("progress");
|
||||
|
||||
DOM.volume.insertBefore(html.icon("volume-high"), DOM.volume.firstChild);
|
||||
DOM.volume = DOM.volume.querySelector("meter");
|
||||
|
||||
DOM.play.addEventListener("click", e => command("play"));
|
||||
DOM.pause.addEventListener("click", e => command("pause 1"));
|
||||
DOM.prev.addEventListener("click", e => command("previous"));
|
||||
|
@ -87,7 +96,7 @@ export function init(n) {
|
|||
DOM.random.addEventListener("click", e => command(`random ${current["random"] == "1" ? "0" : "1"}`));
|
||||
DOM.repeat.addEventListener("click", e => command(`repeat ${current["repeat"] == "1" ? "0" : "1"}`));
|
||||
|
||||
DOM.elapsed.addEventListener("click", e => {
|
||||
DOM.progress.addEventListener("click", e => {
|
||||
let rect = e.target.getBoundingClientRect();
|
||||
let frac = (e.clientX - rect.left) / rect.width;
|
||||
command(`seekcur ${frac * e.target.max}`);
|
||||
|
|
|
@ -35,14 +35,6 @@ function setColor(color) {
|
|||
document.documentElement.dataset.color = color;
|
||||
}
|
||||
|
||||
async function setVolume(volume) {
|
||||
mpd.command(`setvol ${volume}`);
|
||||
}
|
||||
|
||||
export async function notifyVolume(volume) {
|
||||
inputs.volume.value = volume;
|
||||
}
|
||||
|
||||
export async function activate() {}
|
||||
|
||||
export function init(n) {
|
||||
|
@ -50,7 +42,6 @@ export function init(n) {
|
|||
|
||||
inputs.theme = n.querySelector("[name=theme]");
|
||||
inputs.color = Array.from(n.querySelectorAll("[name=color]"));
|
||||
inputs.volume = n.querySelector("[name=volume]");
|
||||
|
||||
load();
|
||||
|
||||
|
@ -58,6 +49,4 @@ export function init(n) {
|
|||
inputs.color.forEach(input => {
|
||||
input.addEventListener("click", e => setColor(e.target.value));
|
||||
});
|
||||
|
||||
inputs.volume.addEventListener("input", e => setVolume(e.target.valueAsNumber));
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue