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()) 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)] #[derive(Deserialize)]
struct PostQueueQuery { struct PostQueueQuery {
path: String, path: String,
@ -80,6 +102,18 @@ async fn post_next(_req: tide::Request<()>) -> tide::Result {
Ok("".into()) 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<()> { async fn sse(_req: tide::Request<()>, sender: tide::sse::Sender) -> tide::Result<()> {
// Needs to be async and all async mpd libraries suck // Needs to be async and all async mpd libraries suck
let mut stream = TcpStream::connect(mpd::HOST).await?; let mut stream = TcpStream::connect(mpd::HOST).await?;
@ -120,6 +154,8 @@ async fn main() -> tide::Result<()> {
app.at("/").get(index); app.at("/").get(index);
app.at("/queue").get(get_queue); 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)); 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; font-family: sans;
background-color: #112; background-color: #112;
color: #fff; color: #fff;
display: flex;
margin: 0;
}
body > div {
padding: 1rem;
}
.browser {
flex: 1;
} }
a { a {
@ -24,12 +34,20 @@
ul { ul {
list-style: none; list-style: none;
padding: 0; padding: 0;
margin: 0;
}
ul.queue {
margin-top: 1.0rem;
} }
ul.queue li { ul.queue li {
padding: .5rem 0; padding: 1.0rem 0.75rem;
border-radius: .25rem;
} }
ul.queue li.playing { ul.queue li.playing {
background-color: #334;
font-weight: bold; font-weight: bold;
} }
@ -60,6 +78,12 @@
align-items: center; align-items: center;
} }
ul.dir li img {
width: 48px;
height: 48px;
object-fit: contain;
}
ul.dir li:hover { ul.dir li:hover {
background-color: #334; background-color: #334;
} }
@ -69,39 +93,55 @@
width: 24px; width: 24px;
} }
.song .song__name { .song__name {
font-weight: bold; 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; font-size: 40px;
cursor: pointer; 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> </style>
</head> </head>
<body hx-sse="connect:/sse"> <body hx-sse="connect:/sse">
<span <div class="browser">
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"> <ul class="breadcrumb">
<li><a href="?path=">Root</a></li> <li><a href="?path=">Root</a></li>
{% for (i, component) in path.iter().enumerate() %} {% for (i, component) in path.iter().enumerate() %}
@ -123,6 +163,7 @@
{% when mpd::Entry::Song with { name, path, artist } %} {% when mpd::Entry::Song with { name, path, artist } %}
<li hx-post="/queue?path={{path}}" hx-swap="none" role="button" > <li hx-post="/queue?path={{path}}" hx-swap="none" role="button" >
<span class="material-symbols-outlined">music_note</span> <span class="material-symbols-outlined">music_note</span>
<!-- img src="/art?path={{path}}" -->
<div class="song"> <div class="song">
<div class="song__name">{{ name }}</div> <div class="song__name">{{ name }}</div>
<div class="song__artist">{{ artist }}</div> <div class="song__artist">{{ artist }}</div>
@ -143,7 +184,12 @@
{% endmatch %} {% endmatch %}
{% endfor %} {% endfor %}
</ul> </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> </body>
</html> </html>