gui functions nearly done
This commit is contained in:
parent
114d6ba4da
commit
f65918db8b
1
Makefile
1
Makefile
|
@ -1,3 +1,4 @@
|
||||||
mpd:
|
mpd:
|
||||||
mkdir -p /tmp/sanic/{music,playlists}
|
mkdir -p /tmp/sanic/{music,playlists}
|
||||||
|
touch /tmp/sanic/mpd_db
|
||||||
mpd --no-daemon ./mpd.conf
|
mpd --no-daemon ./mpd.conf
|
||||||
|
|
64
mpd.go
64
mpd.go
|
@ -3,7 +3,6 @@ package main
|
||||||
import (
|
import (
|
||||||
"github.com/fhs/gompd/v2/mpd"
|
"github.com/fhs/gompd/v2/mpd"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
@ -15,13 +14,13 @@ func updateDb(c echo.Context) error {
|
||||||
// Connect to MPD server
|
// Connect to MPD server
|
||||||
conn, err := mpd.Dial("tcp", "localhost:6600")
|
conn, err := mpd.Dial("tcp", "localhost:6600")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
c.Logger().Error(err)
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
jobId, err := conn.Update("")
|
jobId, err := conn.Update("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
c.Logger().Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.String(http.StatusOK, strconv.Itoa(jobId))
|
return c.String(http.StatusOK, strconv.Itoa(jobId))
|
||||||
|
@ -31,13 +30,13 @@ func previousTrack(c echo.Context) error {
|
||||||
// Connect to MPD server
|
// Connect to MPD server
|
||||||
conn, err := mpd.Dial("tcp", "localhost:6600")
|
conn, err := mpd.Dial("tcp", "localhost:6600")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
c.Logger().Error(err)
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
err = conn.Previous()
|
err = conn.Previous()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
c.Logger().Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.String(http.StatusOK, "")
|
return c.String(http.StatusOK, "")
|
||||||
|
@ -47,13 +46,13 @@ func nextTrack(c echo.Context) error {
|
||||||
// Connect to MPD server
|
// Connect to MPD server
|
||||||
conn, err := mpd.Dial("tcp", "localhost:6600")
|
conn, err := mpd.Dial("tcp", "localhost:6600")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
c.Logger().Error(err)
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
err = conn.Next()
|
err = conn.Next()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
c.Logger().Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.String(http.StatusOK, "")
|
return c.String(http.StatusOK, "")
|
||||||
|
@ -63,13 +62,13 @@ func stopPlayback(c echo.Context) error {
|
||||||
// Connect to MPD server
|
// Connect to MPD server
|
||||||
conn, err := mpd.Dial("tcp", "localhost:6600")
|
conn, err := mpd.Dial("tcp", "localhost:6600")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
c.Logger().Error(err)
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
err = conn.Stop()
|
err = conn.Stop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
c.Logger().Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.String(http.StatusOK, "")
|
return c.String(http.StatusOK, "")
|
||||||
|
@ -79,13 +78,24 @@ func resumePlayback(c echo.Context) error {
|
||||||
// Connect to MPD server
|
// Connect to MPD server
|
||||||
conn, err := mpd.Dial("tcp", "localhost:6600")
|
conn, err := mpd.Dial("tcp", "localhost:6600")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
c.Logger().Error(err)
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
err = conn.Pause(false)
|
status, err := conn.Status()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
c.Logger().Error(err)
|
||||||
|
}
|
||||||
|
if status["state"] == "stop" {
|
||||||
|
err := conn.Play(-1)
|
||||||
|
if err != nil {
|
||||||
|
c.Logger().Error(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = conn.Pause(false)
|
||||||
|
if err != nil {
|
||||||
|
c.Logger().Error(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.String(http.StatusOK, "")
|
return c.String(http.StatusOK, "")
|
||||||
|
@ -95,13 +105,13 @@ func pausePlayback(c echo.Context) error {
|
||||||
// Connect to MPD server
|
// Connect to MPD server
|
||||||
conn, err := mpd.Dial("tcp", "localhost:6600")
|
conn, err := mpd.Dial("tcp", "localhost:6600")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
c.Logger().Error(err)
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
err = conn.Pause(true)
|
err = conn.Pause(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
c.Logger().Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.String(http.StatusOK, "")
|
return c.String(http.StatusOK, "")
|
||||||
|
@ -111,13 +121,13 @@ func seek(c echo.Context) error {
|
||||||
// Connect to MPD server
|
// Connect to MPD server
|
||||||
conn, err := mpd.Dial("tcp", "localhost:6600")
|
conn, err := mpd.Dial("tcp", "localhost:6600")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
c.Logger().Error(err)
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
seconds, err := strconv.Atoi(c.Param("seconds"))
|
seconds, err := strconv.Atoi(c.Param("seconds"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
c.Logger().Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if seconds < 0 {
|
if seconds < 0 {
|
||||||
|
@ -126,7 +136,7 @@ func seek(c echo.Context) error {
|
||||||
|
|
||||||
err = conn.SeekCur(time.Duration(seconds)*time.Second, false)
|
err = conn.SeekCur(time.Duration(seconds)*time.Second, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
c.Logger().Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.String(http.StatusOK, "")
|
return c.String(http.StatusOK, "")
|
||||||
|
@ -136,13 +146,13 @@ func toggleRepeat(c echo.Context) error {
|
||||||
// Connect to MPD server
|
// Connect to MPD server
|
||||||
conn, err := mpd.Dial("tcp", "localhost:6600")
|
conn, err := mpd.Dial("tcp", "localhost:6600")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
c.Logger().Error(err)
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
status, err := conn.Status()
|
status, err := conn.Status()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
c.Logger().Error(err)
|
||||||
}
|
}
|
||||||
if status["repeat"] == "1" {
|
if status["repeat"] == "1" {
|
||||||
err = conn.Repeat(false)
|
err = conn.Repeat(false)
|
||||||
|
@ -150,7 +160,7 @@ func toggleRepeat(c echo.Context) error {
|
||||||
err = conn.Repeat(true)
|
err = conn.Repeat(true)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
c.Logger().Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.String(http.StatusOK, "")
|
return c.String(http.StatusOK, "")
|
||||||
|
@ -160,21 +170,21 @@ func toggleRandom(c echo.Context) error {
|
||||||
// Connect to MPD server
|
// Connect to MPD server
|
||||||
conn, err := mpd.Dial("tcp", "localhost:6600")
|
conn, err := mpd.Dial("tcp", "localhost:6600")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
c.Logger().Error(err)
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
status, err := conn.Status()
|
status, err := conn.Status()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
c.Logger().Error(err)
|
||||||
}
|
}
|
||||||
if status["toggleRandom"] == "1" {
|
if status["random"] == "1" {
|
||||||
err = conn.Random(false)
|
err = conn.Random(false)
|
||||||
} else {
|
} else {
|
||||||
err = conn.Random(true)
|
err = conn.Random(true)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
c.Logger().Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.String(http.StatusOK, "")
|
return c.String(http.StatusOK, "")
|
||||||
|
@ -184,13 +194,13 @@ func setVolume(c echo.Context) error {
|
||||||
// Connect to MPD server
|
// Connect to MPD server
|
||||||
conn, err := mpd.Dial("tcp", "localhost:6600")
|
conn, err := mpd.Dial("tcp", "localhost:6600")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
c.Logger().Error(err)
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
level, err := strconv.Atoi(c.Param("level"))
|
level, err := strconv.Atoi(c.Param("level"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
c.Logger().Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if level > 100 || level < 0 {
|
if level > 100 || level < 0 {
|
||||||
|
@ -199,7 +209,7 @@ func setVolume(c echo.Context) error {
|
||||||
|
|
||||||
err = conn.SetVolume(level)
|
err = conn.SetVolume(level)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
c.Logger().Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.String(http.StatusOK, "")
|
return c.String(http.StatusOK, "")
|
||||||
|
|
24
server.go
24
server.go
|
@ -107,7 +107,12 @@ func wsServe(c echo.Context) error {
|
||||||
// Connect to MPD server
|
// Connect to MPD server
|
||||||
mpdConn, err := mpd.Dial("tcp", "localhost:6600")
|
mpdConn, err := mpd.Dial("tcp", "localhost:6600")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
//log.Fatalln(err)
|
||||||
|
c.Logger().Error(err)
|
||||||
|
err = websocket.Message.Send(ws, fmt.Sprintf("{\"mpd_error\":\"%s\"}", err.Error()))
|
||||||
|
if err != nil {
|
||||||
|
c.Logger().Error(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
defer mpdConn.Close()
|
defer mpdConn.Close()
|
||||||
|
|
||||||
|
@ -121,16 +126,23 @@ func wsServe(c echo.Context) error {
|
||||||
} else {
|
} else {
|
||||||
log.Println(msg)
|
log.Println(msg)
|
||||||
if strings.ToLower(msg) == "#status" {
|
if strings.ToLower(msg) == "#status" {
|
||||||
// TODO: Get current MPD status and return it
|
|
||||||
status, err := mpdConn.Status()
|
status, err := mpdConn.Status()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
c.Logger().Error(err)
|
||||||
}
|
}
|
||||||
jsonData, err := json.Marshal(status)
|
currentsong, err := mpdConn.CurrentSong()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
c.Logger().Error(err)
|
||||||
}
|
}
|
||||||
err = websocket.Message.Send(ws, fmt.Sprintf("{\"mpd_status\":%s}", string(jsonData)))
|
jsonStatus, err := json.Marshal(status)
|
||||||
|
if err != nil {
|
||||||
|
c.Logger().Error(err)
|
||||||
|
}
|
||||||
|
jsonCurrentSong, err := json.Marshal(currentsong)
|
||||||
|
if err != nil {
|
||||||
|
c.Logger().Error(err)
|
||||||
|
}
|
||||||
|
err = websocket.Message.Send(ws, fmt.Sprintf("{\"mpd_status\":%s,\"mpd_current_song\":%s}", string(jsonStatus), string(jsonCurrentSong)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Logger().Error(err)
|
c.Logger().Error(err)
|
||||||
}
|
}
|
||||||
|
|
172
static/controls.js
vendored
172
static/controls.js
vendored
|
@ -1,7 +1,9 @@
|
||||||
const API_URL = `${document.location.protocol}://${document.location.host}/api`;
|
const API_URL = `${document.location.protocol}//${document.location.host}/api`;
|
||||||
|
const VOLUME_STEP = 5;
|
||||||
|
|
||||||
// Get control elements
|
// Get control elements
|
||||||
|
|
||||||
|
const connection_state = document.getElementById("connection-state");
|
||||||
const control_update_db = document.getElementById("control-update-db");
|
const control_update_db = document.getElementById("control-update-db");
|
||||||
const control_previous = document.getElementById("control-previous");
|
const control_previous = document.getElementById("control-previous");
|
||||||
const control_play_pause = document.getElementById("control-play-pause");
|
const control_play_pause = document.getElementById("control-play-pause");
|
||||||
|
@ -13,18 +15,31 @@ const control_shuffle = document.getElementById("control-shuffle");
|
||||||
const control_xfade = document.getElementById("control-xfade");
|
const control_xfade = document.getElementById("control-xfade");
|
||||||
const control_xfade_minus = document.getElementById("control-xfade-minus");
|
const control_xfade_minus = document.getElementById("control-xfade-minus");
|
||||||
const control_xfade_plus = document.getElementById("control-xfade-plus");
|
const control_xfade_plus = document.getElementById("control-xfade-plus");
|
||||||
|
const control_volume = document.getElementById("control-volume");
|
||||||
|
const control_volume_up = document.getElementById("control-volume-up");
|
||||||
|
const control_volume_down = document.getElementById("control-volume-down");
|
||||||
const queue_table = document.querySelector("#queue tbody");
|
const queue_table = document.querySelector("#queue tbody");
|
||||||
|
const control_track = document.getElementById("control-track");
|
||||||
|
const control_time = document.getElementById("control-time");
|
||||||
|
|
||||||
// Add API calls to controls
|
// Add API calls to controls
|
||||||
|
|
||||||
control_update_db.addEventListener("click", e => {
|
control_update_db.addEventListener("click", e => {
|
||||||
fetch(`${API_URL}/update_db`);
|
console.log("Issuing database update")
|
||||||
|
fetch(`${API_URL}/update_db`).then(async r => {
|
||||||
|
if (r.status === 200) {
|
||||||
|
const job_id = await r.text();
|
||||||
|
console.log(`Update started (Job ID: ${job_id})`)
|
||||||
|
} else {
|
||||||
|
console.error(`API returned ${r.status}: ${r.statusText}`)
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
control_previous.addEventListener("click", e => {
|
control_previous.addEventListener("click", e => {
|
||||||
fetch(`${API_URL}/previous_track`);
|
fetch(`${API_URL}/previous_track`);
|
||||||
});
|
});
|
||||||
control_play_pause.addEventListener("click", e => {
|
control_play_pause.addEventListener("click", e => {
|
||||||
if (e.target.innerText === "⏵︎") { // Play
|
if (e.target.innerHTML === "⏵︎") { // TODO: check is never true
|
||||||
fetch(`${API_URL}/pause`);
|
fetch(`${API_URL}/pause`);
|
||||||
} else { // Pause
|
} else { // Pause
|
||||||
fetch(`${API_URL}/play`);
|
fetch(`${API_URL}/play`);
|
||||||
|
@ -40,10 +55,20 @@ control_progress.addEventListener("change", e => {
|
||||||
fetch(`${API_URL}/seek/${e.target.value}`)
|
fetch(`${API_URL}/seek/${e.target.value}`)
|
||||||
});
|
});
|
||||||
control_repeat.addEventListener("click", e => {
|
control_repeat.addEventListener("click", e => {
|
||||||
|
if (e.target.innerHTML === "🔴 repeat") { // TODO: check is never true
|
||||||
|
e.target.innerHTML = "🔘 repeat";
|
||||||
|
} else {
|
||||||
|
e.target.innerHTML = "🔴 repeat";
|
||||||
|
}
|
||||||
fetch(`${API_URL}/repeat`);
|
fetch(`${API_URL}/repeat`);
|
||||||
});
|
});
|
||||||
control_shuffle.addEventListener("click", e => {
|
control_shuffle.addEventListener("click", e => {
|
||||||
fetch(`${API_URL}/shuffle`);
|
if (e.target.innerHTML === "🔴 shuffle") { // TODO: check is never true
|
||||||
|
e.target.innerHTML = "🔘 shuffle";
|
||||||
|
} else {
|
||||||
|
e.target.innerHTML = "🔴 shuffle";
|
||||||
|
}
|
||||||
|
fetch(`${API_URL}/random`);
|
||||||
});
|
});
|
||||||
control_xfade_minus.addEventListener("click", e => {
|
control_xfade_minus.addEventListener("click", e => {
|
||||||
// TODO: not yet implemented
|
// TODO: not yet implemented
|
||||||
|
@ -53,46 +78,129 @@ control_xfade_plus.addEventListener("click", e => {
|
||||||
// TODO: not yet implemented
|
// TODO: not yet implemented
|
||||||
fetch(`${API_URL}/xfade`);
|
fetch(`${API_URL}/xfade`);
|
||||||
});
|
});
|
||||||
|
control_volume_up.addEventListener("click", e => {
|
||||||
|
const v = Math.min(parseInt(control_volume.value) + VOLUME_STEP, 100);
|
||||||
|
fetch(`${API_URL}/volume/${v}`);
|
||||||
|
control_volume.value = v;
|
||||||
|
|
||||||
|
});
|
||||||
|
control_volume_down.addEventListener("click", e => {
|
||||||
|
const v = Math.max(parseInt(control_volume.value) - VOLUME_STEP, 0);
|
||||||
|
fetch(`${API_URL}/volume/${v}`);
|
||||||
|
control_volume.value = v;
|
||||||
|
});
|
||||||
|
control_volume.addEventListener("change", e => {
|
||||||
|
fetch(`${API_URL}/volume/${e.target.value}`);
|
||||||
|
});
|
||||||
|
|
||||||
// Create WebSocket connection.
|
// Create WebSocket connection.
|
||||||
const socket = new WebSocket(`${document.location.protocol === "https" ? "wss" : "ws"}://${document.location.host}/ws`);
|
const socket = new WebSocket(`${document.location.protocol === "https:" ? "wss" : "ws"}://${document.location.host}/ws`);
|
||||||
|
|
||||||
// Connection opened
|
// Connection opened
|
||||||
socket.addEventListener("open", (e) => {
|
socket.addEventListener("open", (e) => {
|
||||||
socket.send("Hello Server!");
|
socket.send("Hello Server!");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Listen for messages
|
// Listen for messages and update UI state
|
||||||
socket.addEventListener("message", (e) => {
|
socket.addEventListener("message", (e) => {
|
||||||
console.log("Message from server");
|
// Print out mpd response
|
||||||
|
console.log(`DEBUG: ${e.data}`); // DEBUG
|
||||||
|
|
||||||
const msg = JSON.parse(e.data);
|
const msg = JSON.parse(e.data);
|
||||||
if ("error" in msg.mpd_status) {
|
|
||||||
console.error(msg.mpd_status.error);
|
if ("mpd_status" in msg) {
|
||||||
|
if (msg.mpd_status == null) {
|
||||||
|
connection_state.innerHTML = "❌ Disconnected"; // ✅ Check Mark Button
|
||||||
|
} else {
|
||||||
|
// print error if present
|
||||||
|
if ("error" in msg.mpd_status) {
|
||||||
|
console.error(msg.mpd_status.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// update "Update DB" button
|
||||||
|
if ("updating_db" in msg.mpd_status) {
|
||||||
|
control_update_db.disabled = true;
|
||||||
|
} else {
|
||||||
|
if (control_update_db.disabled) {
|
||||||
|
console.log("Database update done.")
|
||||||
|
}
|
||||||
|
control_update_db.disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update play/pause button
|
||||||
|
if ("state" in msg.mpd_status && msg.mpd_status.state === "play") {
|
||||||
|
control_play_pause.innerHTML = "⏸︎"; // Pause
|
||||||
|
} else {
|
||||||
|
control_play_pause.innerHTML = "⏵︎"; // Play
|
||||||
|
}
|
||||||
|
|
||||||
|
// update playback time
|
||||||
|
if ("elapsed" in msg.mpd_status && "duration" in msg.mpd_status) {
|
||||||
|
const elapsed_hours = Math.floor(msg.mpd_status.elapsed / 3600);
|
||||||
|
const elapsed_minutes = Math.floor((msg.mpd_status.elapsed - elapsed_hours * 3600) / 60);
|
||||||
|
const elapsed_seconds = Math.floor(msg.mpd_status.elapsed - elapsed_hours * 3600 - elapsed_minutes * 60);
|
||||||
|
const duration_hours = Math.floor(msg.mpd_status.duration / 3600);
|
||||||
|
const duration_minutes = Math.floor((msg.mpd_status.duration - duration_hours * 3600) / 60);
|
||||||
|
const duration_seconds = Math.floor(msg.mpd_status.duration - duration_hours * 3600 - duration_minutes * 60);
|
||||||
|
control_time.value = `${elapsed_hours}:${elapsed_minutes.toString().padStart(2, '0')}:${elapsed_seconds.toString().padStart(2, '0')}/${duration_hours}:${duration_minutes.toString().padStart(2, '0')}:${duration_seconds.toString().padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
if ("elapsed" in msg.mpd_status) {
|
||||||
|
control_progress.value = msg.mpd_status.elapsed;
|
||||||
|
}
|
||||||
|
if ("duration" in msg.mpd_status) {
|
||||||
|
control_progress.max = msg.mpd_status.duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update repeat state
|
||||||
|
if ("repeat" in msg.mpd_status) {
|
||||||
|
if (msg.mpd_status.repeat === "1") {
|
||||||
|
control_repeat.innerHTML = "🔴 repeat"; // 🔴 Red Circle
|
||||||
|
} else {
|
||||||
|
control_repeat.innerHTML = "🔘 repeat"; // 🔘 Radio Button
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update shuffle state
|
||||||
|
if ("random" in msg.mpd_status) {
|
||||||
|
if (msg.mpd_status.random === "1") {
|
||||||
|
control_shuffle.innerHTML = "🔴 shuffle"; // 🔴 Red Circle
|
||||||
|
} else {
|
||||||
|
control_shuffle.innerHTML = "🔘 shuffle"; // 🔘 Radio Button
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update crossfade state
|
||||||
|
if ("xfade" in msg.mpd_status) {
|
||||||
|
control_xfade.value = msg.mpd_status.xfade;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update volume
|
||||||
|
if ("volume" in msg.mpd_status) {
|
||||||
|
control_volume.value = msg.mpd_status.volume;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("updating_db" in msg.mpd_status) {
|
// update song info
|
||||||
control_update_db.disable();
|
if ("mpd_current_song" in msg && msg.mpd_current_song != null) {
|
||||||
} else {
|
if ("Artist" in msg.mpd_current_song && "Title" in msg.mpd_current_song) {
|
||||||
control_update_db.enable();
|
control_track.value = `${msg.mpd_current_song.Artist} - ${msg.mpd_current_song.Title}`
|
||||||
|
} else {
|
||||||
|
control_track.value = msg.mpd_current_song.file;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if ("state" in msg.mpd_status && msg.mpd_status.state === "play") {
|
|
||||||
control_play_pause.innerText = "⏸︎"; // Pause
|
if ("mpd_error" in msg) {
|
||||||
} else {
|
console.error(`MPD Error: ${msg.mpd_error}`)
|
||||||
control_play_pause.innerText = "⏵︎"; // Play
|
|
||||||
}
|
|
||||||
if ("elapsed" in msg.mpd_status) {
|
|
||||||
control_progress.value = msg.mpd_status.elapsed;
|
|
||||||
}
|
|
||||||
if ("duration" in msg.mpd_status) {
|
|
||||||
control_progress.max = msg.mpd_status.duration;
|
|
||||||
}
|
|
||||||
if ("repeat" in msg.mpd_status) {
|
|
||||||
control_repeat.checked = msg.mpd_status.repeat;
|
|
||||||
}
|
|
||||||
if ("random" in msg.mpd_status) {
|
|
||||||
control_shuffle.checked = msg.mpd_status.random;
|
|
||||||
}
|
|
||||||
if ("xfade" in msg.mpd_status) {
|
|
||||||
control_xfade.value = msg.mpd_status.xfade;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Request MPD status every second
|
||||||
|
window.setInterval(() => {
|
||||||
|
if (socket.readyState === socket.OPEN) {
|
||||||
|
socket.send("#status");
|
||||||
|
connection_state.innerHTML = "✅ Connected"; // ❌ Cross Mark
|
||||||
|
} else {
|
||||||
|
connection_state.innerHTML = "❌ Disconnected"; // ✅ Check Mark Button
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en" xmlns="http://www.w3.org/1999/html">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<title>Sanic</title>
|
<title>Sanic</title>
|
||||||
|
@ -9,13 +9,14 @@
|
||||||
<body>
|
<body>
|
||||||
<main>
|
<main>
|
||||||
<div id="nav">
|
<div id="nav">
|
||||||
<div>
|
<div id="control-admin">
|
||||||
|
<button id="connection-state">❌ Disconnected</button> <!-- ❌ Cross Mark -->
|
||||||
<button>Login</button>
|
<button>Login</button>
|
||||||
<button>Config</button>
|
<button>Config</button>
|
||||||
<button id="control-update-db" disabled="disabled">Update DB</button>
|
<button id="control-update-db" disabled="disabled">Update DB</button>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div class="spaced">
|
||||||
<button id="control-previous">⏮︎</button> <!-- ⏮️ Last Track Button -->
|
<button id="control-previous">⏮︎</button> <!-- ⏮️ Last Track Button -->
|
||||||
<button id="control-stop">⏹︎</button> <!-- ⏹️ Stop Button -->
|
<button id="control-stop">⏹︎</button> <!-- ⏹️ Stop Button -->
|
||||||
<button id="control-play-pause">⏵︎</button> <!-- ▶️ Play or ⏸️ Pause Button -->
|
<button id="control-play-pause">⏵︎</button> <!-- ▶️ Play or ⏸️ Pause Button -->
|
||||||
|
@ -27,24 +28,38 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div class="spaced">
|
||||||
|
<button id="control-repeat">🔘 repeat</button>
|
||||||
|
<button id="control-shuffle">🔘 shuffle</button>
|
||||||
|
</div>
|
||||||
|
<div class="spaced">
|
||||||
|
<label for="control-xfade">xfade</label>
|
||||||
<div>
|
<div>
|
||||||
<div>
|
|
||||||
<label for="control-repeat">repeat</label>
|
|
||||||
<input type="checkbox" id="control-repeat" name="repeat" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="control-shuffle">shuffle</label>
|
|
||||||
<input type="checkbox" id="control-shuffle" name="shuffle" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="control-xfade">xfade</label>
|
|
||||||
<button id="control-xfade-minus">➖</button>
|
<button id="control-xfade-minus">➖</button>
|
||||||
<input type="number" id="control-xfade" name="xfade" value="00" />
|
<input type="number" id="control-xfade" name="xfade" value="00" />
|
||||||
<button id="control-xfade-plus">➕</button>
|
<button id="control-xfade-plus">➕</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="spaced">
|
||||||
|
<button id="control-volume-down">🔉</button> <!-- 🔉 Speaker with sound wave -->
|
||||||
|
<input id="control-volume" name="volume" type="range" min="0" max="100" value="50" />
|
||||||
|
<button id="control-volume-up">🔊</button> <!-- 🔊 Speaker with sound waves -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p>Now playing:</p>
|
||||||
|
<div>
|
||||||
|
<label for="control-track">Track:</label>
|
||||||
|
<input type="text" id="control-track" name="track" disabled="disabled" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="control-time">Time:</label>
|
||||||
|
<input type="text" id="control-time" name="time" value="00:00:00/00:00:00" disabled="disabled" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<img id="sanic-logo" alt="sanic logo" src="/img/sanic-logo.webp" />
|
||||||
|
<div>Sanic © 2023</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="queue">
|
<div id="queue">
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* #################### */
|
/* #################### */
|
||||||
/* ####### main ####### */
|
/* #### structure ##### */
|
||||||
/* #################### */
|
/* #################### */
|
||||||
|
|
||||||
html, body { margin: 0; height: 100%; }
|
html, body { margin: 0; height: 100%; }
|
||||||
|
@ -37,6 +37,21 @@ table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#control-admin {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sanic-logo {
|
||||||
|
max-width: 80%;
|
||||||
|
max-height: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spaced {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
/* #################### */
|
/* #################### */
|
||||||
/* ###### debug ####### */
|
/* ###### debug ####### */
|
||||||
/* #################### */
|
/* #################### */
|
||||||
|
@ -62,3 +77,58 @@ div {
|
||||||
#control-xfade[type=number] {
|
#control-xfade[type=number] {
|
||||||
-moz-appearance: textfield;
|
-moz-appearance: textfield;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#control-xfade {
|
||||||
|
width: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#control-previous,
|
||||||
|
#control-play-pause,
|
||||||
|
#control-stop,
|
||||||
|
#control-next {
|
||||||
|
width: 2.5em;
|
||||||
|
height: 2.5em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
#control-track {
|
||||||
|
transform: translateX(100%);
|
||||||
|
-moz-transform: translateX(100%);
|
||||||
|
-webkit-transform: translateX(100%);
|
||||||
|
animation: scroll-left 20s linear infinite;
|
||||||
|
-moz-animation: scroll-left 2s linear infinite;
|
||||||
|
-webkit-animation: scroll-left 2s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes scroll-left {
|
||||||
|
0% {
|
||||||
|
transform: translateX(100%);
|
||||||
|
-moz-transform: translateX(100%);
|
||||||
|
-webkit-transform: translateX(100%);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
-moz-transform: translateX(-100%);
|
||||||
|
-webkit-transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@-moz-keyframes scroll-left {
|
||||||
|
0% {
|
||||||
|
-moz-transform: translateX(100%);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
-moz-transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes scroll-left {
|
||||||
|
0% {
|
||||||
|
-webkit-transform: translateX(100%);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
-webkit-transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
Loading…
Reference in a new issue