Implement "now playing" card

This commit is contained in:
Sijmen 2023-04-25 16:32:51 +02:00
parent 96a514d3c6
commit adfddfc9cf
Signed by: vijfhoek
GPG key ID: DAF7821E067D9C48
3 changed files with 182 additions and 63 deletions

View file

@ -49,6 +49,28 @@ async fn get_queue(_req: tide::Request<()>) -> tide::Result {
Ok(template.into())
}
#[derive(Template)]
#[template(path = "current.html")]
struct CurrentTemplate {
song: Option<mpdrs::Song>,
name: Option<String>,
}
async fn get_current(_req: tide::Request<()>) -> tide::Result {
let mut mpd = mpd::connect()?;
let song = mpd.currentsong()?;
let mut template = CurrentTemplate { song: song.clone(), name: None };
if let Some(song) = song {
let name = song.title.unwrap_or(song.file.clone()).to_string();
template.name = Some(name);
}
Ok(template.into())
}
#[derive(Deserialize)]
struct PostQueueQuery {
path: String,
@ -80,6 +102,18 @@ async fn post_next(_req: tide::Request<()>) -> tide::Result {
Ok("".into())
}
async fn get_art(req: tide::Request<()>) -> tide::Result {
let query: IndexQuery = req.query()?;
let resp = if let Ok(art) = mpd::connect()?.albumart(&query.path) {
tide::Response::builder(tide::StatusCode::Ok)
.body(art)
.header("cache-control", "max-age=3600")
} else {
tide::Response::builder(tide::StatusCode::NotFound)
};
Ok(resp.into())
}
async fn sse(_req: tide::Request<()>, sender: tide::sse::Sender) -> tide::Result<()> {
// Needs to be async and all async mpd libraries suck
let mut stream = TcpStream::connect(mpd::HOST).await?;
@ -120,6 +154,8 @@ async fn main() -> tide::Result<()> {
app.at("/").get(index);
app.at("/queue").get(get_queue);
app.at("/current").get(get_current);
app.at("/art").get(get_art);
app.at("/sse").get(tide::sse::endpoint(sse));

37
templates/current.html Normal file
View file

@ -0,0 +1,37 @@
{# #}
<!DOCTYPE html>
<div class="nowplaying">
<div class="current">
{% if let Some(song) = song %}
<img class="albumart" src="/art?path={{ song.file }}">
<div class="metadata">
{% if let Some(name) = name %}
<div class="song__name">{{ name }}</div>
{% endif %}
{% if let Some(artist) = song.artist %}
<div>{{ artist }}</div>
{% endif %}
</div>
{% endif %}
</div>
<div class="controls">
<span
hx-post="/previous" hx-swap="none"
class="control material-symbols-outlined" role="button"
>skip_previous</span>
<span
hx-post="/play" hx-swap="none"
class="control material-symbols-outlined" role="button"
>play_arrow</span>
<span
hx-post="/pause" hx-swap="none"
class="control material-symbols-outlined" role="button"
>pause</span>
<span
hx-post="/next" hx-swap="none"
class="control material-symbols-outlined" role="button"
>skip_next</span>
</div>
</div>

View file

@ -13,6 +13,16 @@
font-family: sans;
background-color: #112;
color: #fff;
display: flex;
margin: 0;
}
body > div {
padding: 1rem;
}
.browser {
flex: 1;
}
a {
@ -24,12 +34,20 @@
ul {
list-style: none;
padding: 0;
margin: 0;
}
ul.queue {
margin-top: 1.0rem;
}
ul.queue li {
padding: .5rem 0;
padding: 1.0rem 0.75rem;
border-radius: .25rem;
}
ul.queue li.playing {
background-color: #334;
font-weight: bold;
}
@ -60,6 +78,12 @@
align-items: center;
}
ul.dir li img {
width: 48px;
height: 48px;
object-fit: contain;
}
ul.dir li:hover {
background-color: #334;
}
@ -69,81 +93,103 @@
width: 24px;
}
.song .song__name {
.song__name {
font-weight: bold;
}
span.control {
.player {
width: 25rem;
}
.player .nowplaying {
flex-flow: column;
background-color: #334;
border-radius: 0.25rem;
}
.player .controls {
display: flex;
justify-content: space-around;
padding: 0.5rem;
}
.player .control {
font-size: 40px;
cursor: pointer;
}
.player .current {
display: flex;
flex-flow: row;
align-items: center;
padding: 0.5rem;
}
.player .albumart {
width: 80px;
height: 80px;
object-fit: contain;
display: block;
margin-right: 1.0rem;
border-radius: 0.25rem;
}
.player .metadata {
flex: 1;
}
</style>
</head>
<body hx-sse="connect:/sse">
<span
hx-post="/previous" hx-swap="none"
class="control material-symbols-outlined" role="button"
>skip_previous</span>
<span
hx-post="/play" hx-swap="none"
class="control material-symbols-outlined" role="button"
>play_arrow</span>
<span
hx-post="/pause" hx-swap="none"
class="control material-symbols-outlined" role="button"
>pause</span>
<span
hx-post="/next" hx-swap="none"
class="control material-symbols-outlined" role="button"
>skip_next</span>
<div hx-trigger="load,sse:queue,sse:player" hx-get="/queue"></div>
<hr>
<ul class="breadcrumb">
<li><a href="?path=">Root</a></li>
{% for (i, component) in path.iter().enumerate() %}
<li>
{% if i == path.len() - 1 %}
{{ component }}
{% else %}
<a href="?path={{ path[..i + 1].join("/") }}">
<div class="browser">
<ul class="breadcrumb">
<li><a href="?path=">Root</a></li>
{% for (i, component) in path.iter().enumerate() %}
<li>
{% if i == path.len() - 1 %}
{{ component }}
</a>
{% endif %}
</li>
{% endfor %}
</ul>
{% else %}
<a href="?path={{ path[..i + 1].join("/") }}">
{{ component }}
</a>
{% endif %}
</li>
{% endfor %}
</ul>
<ul class="dir">
{% for entry in entries %}
{% match entry %}
{% when mpd::Entry::Song with { name, path, artist } %}
<li hx-post="/queue?path={{path}}" hx-swap="none" role="button" >
<span class="material-symbols-outlined">music_note</span>
<div class="song">
<div class="song__name">{{ name }}</div>
<div class="song__artist">{{ artist }}</div>
</a>
</li>
{% when mpd::Entry::Directory with { name, path }%}
<li onclick="window.location = '?path={{path}}'">
<span class="material-symbols-outlined">folder</span>
<a href="?path={{path}}">{{ name }}</a>
</li>
{% when mpd::Entry::Playlist with { name, path } %}
<li hx-post="/queue?path={{path}}" hx-swap="none" role="button" >
<span class="material-symbols-outlined">playlist_play</span>
<div class="song">
<div class="song__name">{{ name }}</div>
</a>
</li>
{% endmatch %}
{% endfor %}
</ul>
<ul class="dir">
{% for entry in entries %}
{% match entry %}
{% when mpd::Entry::Song with { name, path, artist } %}
<li hx-post="/queue?path={{path}}" hx-swap="none" role="button" >
<span class="material-symbols-outlined">music_note</span>
<!-- img src="/art?path={{path}}" -->
<div class="song">
<div class="song__name">{{ name }}</div>
<div class="song__artist">{{ artist }}</div>
</a>
</li>
{% when mpd::Entry::Directory with { name, path }%}
<li onclick="window.location = '?path={{path}}'">
<span class="material-symbols-outlined">folder</span>
<a href="?path={{path}}">{{ name }}</a>
</li>
{% when mpd::Entry::Playlist with { name, path } %}
<li hx-post="/queue?path={{path}}" hx-swap="none" role="button" >
<span class="material-symbols-outlined">playlist_play</span>
<div class="song">
<div class="song__name">{{ name }}</div>
</a>
</li>
{% endmatch %}
{% endfor %}
</ul>
</div>
<div class="player">
<div hx-trigger="load,sse:queue,sse:player" hx-get="/current"></div>
<div hx-trigger="load,sse:queue,sse:player" hx-get="/queue"></div>
</div>
</body>
</html>