player: progress, meter

This commit is contained in:
Ondrej Zara 2019-04-11 13:34:24 +02:00
parent e75ce14ec0
commit ed1eeadad5
9 changed files with 120 additions and 44 deletions

View file

@ -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;

View file

@ -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 {

View 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

View file

@ -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">

View file

@ -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);
}

View file

@ -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>`;

View file

@ -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;
}

View file

@ -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}`);

View file

@ -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));
}