Initial commit
This commit is contained in:
parent
a8ca5c3677
commit
cc32fb9a2d
|
@ -0,0 +1 @@
|
||||||
|
/target
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,19 @@
|
||||||
|
[package]
|
||||||
|
name = "greg-ng"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.82"
|
||||||
|
axum = { version = "0.6.20", features = ["macros"] }
|
||||||
|
clap = { version = "4.4.1", features = ["derive"] }
|
||||||
|
env_logger = "0.10.0"
|
||||||
|
log = "0.4.20"
|
||||||
|
mpvipc = "1.3.0"
|
||||||
|
serde = { version = "1.0.188", features = ["derive"] }
|
||||||
|
serde_json = "1.0.105"
|
||||||
|
tokio = { version = "1.32.0", features = ["full"] }
|
||||||
|
tower = { version = "0.4.13", features = ["full"] }
|
||||||
|
tower-http = { version = "0.4.3", features = ["full"] }
|
20
README.md
20
README.md
|
@ -1,3 +1,19 @@
|
||||||
# greg-ng
|
# Greg-ng
|
||||||
|
|
||||||
Georg has never been this polish
|
New implementation of https://github.com/Programvareverkstedet/grzegorz
|
||||||
|
|
||||||
|
## Feature wishlist
|
||||||
|
|
||||||
|
- [ ] Save playlists to machine
|
||||||
|
- [ ] Cache playlist contents to disk
|
||||||
|
- [ ] Expose service through mpd protocol
|
||||||
|
- [ ] Users with playlists and songs (and auth?)
|
||||||
|
- [ ] Some kind of fair scheduling for each user
|
||||||
|
- [ ] Max time to avoid playlist songs
|
||||||
|
- [ ] Expose video/media stream so others can listen at home
|
||||||
|
- [ ] Syncope support >:)
|
||||||
|
- [ ] Jitsi support >:)))
|
||||||
|
- [ ] Show other media while playing music, like grafana or bustimes
|
||||||
|
- [ ] Soft shuffle
|
||||||
|
- [ ] Libre.fm integration
|
||||||
|
- [ ] Karaoke mode lmao
|
||||||
|
|
|
@ -0,0 +1,310 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use axum::{
|
||||||
|
extract::{Query, State},
|
||||||
|
response::IntoResponse,
|
||||||
|
routing::{delete, get, post},
|
||||||
|
Json, Router,
|
||||||
|
};
|
||||||
|
use mpvipc::{
|
||||||
|
Mpv, NumberChangeOptions, Playlist, PlaylistAddOptions, PlaylistAddTypeOptions, SeekOptions,
|
||||||
|
Switch,
|
||||||
|
};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde_json::{json, Value};
|
||||||
|
|
||||||
|
type Result<T, E = crate::app_error::AppError> = std::result::Result<T, E>;
|
||||||
|
|
||||||
|
pub fn api_routes(mpv: Mpv) -> Router {
|
||||||
|
Router::new()
|
||||||
|
.route("/", get(index))
|
||||||
|
.route("/load", post(loadfile))
|
||||||
|
.route("/play", get(play_get))
|
||||||
|
.route("/play", post(play_set))
|
||||||
|
.route("/volume", get(volume_get))
|
||||||
|
.route("/volume", post(volume_set))
|
||||||
|
.route("/time", get(time_get))
|
||||||
|
.route("/time", post(time_set))
|
||||||
|
.route("/playlist", get(playlist_get))
|
||||||
|
.route("/playlist/next", post(playlist_next))
|
||||||
|
.route("/playlist/previous", post(playlist_previous))
|
||||||
|
.route("/playlist/goto", post(playlist_goto))
|
||||||
|
.route("/playlist/remove", delete(playlist_remove_or_clear))
|
||||||
|
.route("/playlist/move", post(playlist_goto))
|
||||||
|
.route("/playlist/shuffle", post(shuffle))
|
||||||
|
.route("/playlist/loop", get(playlist_get_looping))
|
||||||
|
.route("/playlist/loop", post(playlist_set_looping))
|
||||||
|
.with_state(Arc::new(Mutex::new(mpv)))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn index() -> &'static str {
|
||||||
|
"Hello friend, I hope you're having a lovely day"
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct APIRequestLoadFile {
|
||||||
|
// Link to the resource to enqueue
|
||||||
|
path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add item to playlist
|
||||||
|
async fn loadfile(
|
||||||
|
State(mpv): State<Arc<Mutex<Mpv>>>,
|
||||||
|
Query(request): Query<APIRequestLoadFile>,
|
||||||
|
) -> Result<impl IntoResponse> {
|
||||||
|
log::trace!("POST /load {:?}", request);
|
||||||
|
|
||||||
|
mpv.lock().await.playlist_add(
|
||||||
|
request.path.as_str(),
|
||||||
|
PlaylistAddTypeOptions::File,
|
||||||
|
PlaylistAddOptions::Append,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(Json(json!({
|
||||||
|
"status": "true".to_string(),
|
||||||
|
"error": false,
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check whether the player is paused or playing
|
||||||
|
async fn play_get(State(mpv): State<Arc<Mutex<Mpv>>>) -> Result<impl IntoResponse> {
|
||||||
|
log::trace!("GET /play");
|
||||||
|
|
||||||
|
let paused: bool = mpv.lock().await.get_property("pause")?;
|
||||||
|
Ok(Json(json!({
|
||||||
|
"value": paused,
|
||||||
|
"error": false,
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct APIRequestPlay {
|
||||||
|
value: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set whether the player is paused or playing
|
||||||
|
async fn play_set(
|
||||||
|
State(mpv): State<Arc<Mutex<Mpv>>>,
|
||||||
|
Query(request): Query<APIRequestPlay>,
|
||||||
|
) -> Result<impl IntoResponse> {
|
||||||
|
log::trace!("POST /play {:?}", request);
|
||||||
|
|
||||||
|
mpv.lock().await.set_property("pause", request.value)?;
|
||||||
|
|
||||||
|
Ok(Json(json!({
|
||||||
|
"error": false,
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the current player volume
|
||||||
|
async fn volume_get(State(mpv): State<Arc<Mutex<Mpv>>>) -> Result<impl IntoResponse> {
|
||||||
|
log::trace!("GET /volume");
|
||||||
|
|
||||||
|
let volume: f64 = mpv.lock().await.get_property("volume")?;
|
||||||
|
|
||||||
|
Ok(Json(json!({
|
||||||
|
"value": volume,
|
||||||
|
"error": false,
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct APIRequestVolume {
|
||||||
|
value: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the player volume
|
||||||
|
async fn volume_set(
|
||||||
|
State(mpv): State<Arc<Mutex<Mpv>>>,
|
||||||
|
Query(request): Query<APIRequestVolume>,
|
||||||
|
) -> Result<impl IntoResponse> {
|
||||||
|
log::trace!("POST /volume {:?}", request);
|
||||||
|
|
||||||
|
mpv.lock()
|
||||||
|
.await
|
||||||
|
.set_volume(request.value, NumberChangeOptions::Absolute)?;
|
||||||
|
|
||||||
|
Ok(Json(json!({
|
||||||
|
"error": false,
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get current playback position
|
||||||
|
async fn time_get(State(mpv): State<Arc<Mutex<Mpv>>>) -> Result<impl IntoResponse> {
|
||||||
|
log::trace!("GET /time");
|
||||||
|
|
||||||
|
let current: f64 = mpv.lock().await.get_property("time-pos")?;
|
||||||
|
let remaining: f64 = mpv.lock().await.get_property("time-remaining")?;
|
||||||
|
let total = current + remaining;
|
||||||
|
|
||||||
|
Ok(Json(json!({
|
||||||
|
"value": {
|
||||||
|
"current": current,
|
||||||
|
"remaining": remaining,
|
||||||
|
"total": total,
|
||||||
|
},
|
||||||
|
"error": false,
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct APIRequestTime {
|
||||||
|
pos: Option<f64>,
|
||||||
|
percent: Option<f64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set playback position
|
||||||
|
async fn time_set(
|
||||||
|
State(mpv): State<Arc<Mutex<Mpv>>>,
|
||||||
|
Query(request): Query<APIRequestTime>,
|
||||||
|
) -> Result<impl IntoResponse> {
|
||||||
|
log::trace!("POST /time {:?}", request);
|
||||||
|
|
||||||
|
if request.pos.is_some() && request.percent.is_some() {
|
||||||
|
return Err(crate::app_error::AppError(anyhow::anyhow!(
|
||||||
|
"pos and percent cannot be provided at the same time"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(pos) = request.pos {
|
||||||
|
mpv.lock().await.seek(pos, SeekOptions::Absolute)?;
|
||||||
|
} else if let Some(percent) = request.percent {
|
||||||
|
mpv.lock()
|
||||||
|
.await
|
||||||
|
.seek(percent, SeekOptions::AbsolutePercent)?;
|
||||||
|
} else {
|
||||||
|
return Err(crate::app_error::AppError(anyhow::anyhow!(
|
||||||
|
"Either pos or percent must be provided"
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Json(json!({
|
||||||
|
"error": false,
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the current playlist
|
||||||
|
async fn playlist_get(State(mpv): State<Arc<Mutex<Mpv>>>) -> Result<impl IntoResponse> {
|
||||||
|
log::trace!("GET /playlist");
|
||||||
|
|
||||||
|
let playlist: Playlist = mpv.lock().await.get_playlist()?;
|
||||||
|
let is_playing: bool = mpv.lock().await.get_property("pause")?;
|
||||||
|
|
||||||
|
let items: Vec<Value> = playlist
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, item)| {
|
||||||
|
json!({
|
||||||
|
"index": i,
|
||||||
|
"current": item.current,
|
||||||
|
"playing": is_playing,
|
||||||
|
"filename": item.filename,
|
||||||
|
"data": {
|
||||||
|
"fetching": true,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(Json(json!({
|
||||||
|
"value": items,
|
||||||
|
"error": false,
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Skip to the next item in the playlist
|
||||||
|
async fn playlist_next(State(mpv): State<Arc<Mutex<Mpv>>>) -> Result<impl IntoResponse> {
|
||||||
|
log::trace!("POST /playlist/next");
|
||||||
|
|
||||||
|
Ok(Json(json!({
|
||||||
|
"status": mpv.lock().await.next().is_ok().to_string(),
|
||||||
|
"error": false,
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Go back to the previous item in the playlist
|
||||||
|
async fn playlist_previous(State(mpv): State<Arc<Mutex<Mpv>>>) -> Result<impl IntoResponse> {
|
||||||
|
log::trace!("POST /playlist/previous");
|
||||||
|
|
||||||
|
Ok(Json(json!({
|
||||||
|
"status": mpv.lock().await.prev().is_ok().to_string(),
|
||||||
|
"error": false,
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct APIRequestPlaylistGoto {
|
||||||
|
index: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Go chosen item in the playlist
|
||||||
|
async fn playlist_goto(
|
||||||
|
State(mpv): State<Arc<Mutex<Mpv>>>,
|
||||||
|
Query(request): Query<APIRequestPlaylistGoto>,
|
||||||
|
) -> Result<impl IntoResponse> {
|
||||||
|
log::trace!("POST /playlist/goto {:?}", request);
|
||||||
|
|
||||||
|
Ok(Json(json!({
|
||||||
|
"status": mpv.lock().await.playlist_play_id(request.index).is_ok().to_string(),
|
||||||
|
"error": false,
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears single item or whole playlist
|
||||||
|
async fn playlist_remove_or_clear(State(mpv): State<Arc<Mutex<Mpv>>>) -> Result<impl IntoResponse> {
|
||||||
|
log::trace!("DELETE /playlist/remove");
|
||||||
|
|
||||||
|
Ok(Json(json!({
|
||||||
|
"status": mpv.lock().await.playlist_clear().is_ok().to_string(),
|
||||||
|
"error": false,
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shuffle the playlist
|
||||||
|
async fn shuffle(State(mpv): State<Arc<Mutex<Mpv>>>) -> Result<impl IntoResponse> {
|
||||||
|
log::trace!("POST /playlist/shuffle");
|
||||||
|
|
||||||
|
Ok(Json(json!({
|
||||||
|
"status": mpv.lock().await.playlist_shuffle().is_ok().to_string(),
|
||||||
|
"error": false,
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See whether it loops the playlist or not
|
||||||
|
async fn playlist_get_looping(State(mpv): State<Arc<Mutex<Mpv>>>) -> Result<impl IntoResponse> {
|
||||||
|
log::trace!("GET /playlist/loop");
|
||||||
|
|
||||||
|
// TODO: this needs to be updated in the next version of the API
|
||||||
|
// let loop_file: bool = mpv.lock().await.get_property("loop-file").unwrap();
|
||||||
|
let loop_playlist: bool = mpv.lock().await.get_property("loop-playlist")?;
|
||||||
|
|
||||||
|
Ok(Json(json!({
|
||||||
|
"value": loop_playlist,
|
||||||
|
"error": false,
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct APIRequestPlaylistSetLooping {
|
||||||
|
r#loop: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn playlist_set_looping(
|
||||||
|
State(mpv): State<Arc<Mutex<Mpv>>>,
|
||||||
|
Query(request): Query<APIRequestPlaylistSetLooping>,
|
||||||
|
) -> Result<impl IntoResponse> {
|
||||||
|
log::trace!("POST /playlist/loop {:?}", request);
|
||||||
|
|
||||||
|
if request.r#loop {
|
||||||
|
mpv.lock().await.set_loop_playlist(Switch::On)?;
|
||||||
|
} else {
|
||||||
|
mpv.lock().await.set_loop_playlist(Switch::Off)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Json(json!({
|
||||||
|
"status": request.r#loop.to_string(),
|
||||||
|
"error": false,
|
||||||
|
})))
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
use axum::{
|
||||||
|
http::StatusCode,
|
||||||
|
response::{IntoResponse, Response},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Make our own error that wraps `anyhow::Error`.
|
||||||
|
pub struct AppError(pub anyhow::Error);
|
||||||
|
|
||||||
|
// Tell axum how to convert `AppError` into a response.
|
||||||
|
impl IntoResponse for AppError {
|
||||||
|
fn into_response(self) -> Response {
|
||||||
|
(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
format!("Something went wrong: {}", self.0),
|
||||||
|
)
|
||||||
|
.into_response()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This enables using `?` on functions that return `Result<_, anyhow::Error>` to turn them into
|
||||||
|
// `Result<_, AppError>`. That way you don't need to do that manually.
|
||||||
|
impl<E> From<E> for AppError
|
||||||
|
where
|
||||||
|
E: Into<anyhow::Error>,
|
||||||
|
{
|
||||||
|
fn from(err: E) -> Self {
|
||||||
|
Self(err.into())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,161 @@
|
||||||
|
use anyhow::Context;
|
||||||
|
use axum::{Router, Server};
|
||||||
|
use clap::Parser;
|
||||||
|
use mpvipc::Mpv;
|
||||||
|
use std::{fs::create_dir_all, net::SocketAddr, path::Path};
|
||||||
|
use tokio::process::{Child, Command};
|
||||||
|
|
||||||
|
mod api;
|
||||||
|
mod app_error;
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
struct Args {
|
||||||
|
#[clap(long, default_value = "localhost")]
|
||||||
|
host: String,
|
||||||
|
|
||||||
|
#[clap(short, long, default_value = "8008")]
|
||||||
|
port: u16,
|
||||||
|
|
||||||
|
#[clap(long, value_name = "PATH", default_value = "/run/mpv/mpv.sock")]
|
||||||
|
mpv_socket_path: String,
|
||||||
|
|
||||||
|
#[clap(long, value_name = "PATH")]
|
||||||
|
mpv_executable_path: Option<String>,
|
||||||
|
|
||||||
|
#[clap(short, long, default_value = "true")]
|
||||||
|
auto_start_mpv: bool,
|
||||||
|
|
||||||
|
#[clap(long, default_value = "true")]
|
||||||
|
force_auto_start: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MpvConnectionArgs {
|
||||||
|
socket_path: String,
|
||||||
|
executable_path: Option<String>,
|
||||||
|
auto_start: bool,
|
||||||
|
force_auto_start: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn connect_to_mpv(args: &MpvConnectionArgs) -> anyhow::Result<(Mpv, Option<Child>)> {
|
||||||
|
log::debug!("Connecting to mpv");
|
||||||
|
|
||||||
|
debug_assert!(
|
||||||
|
!args.force_auto_start || args.auto_start,
|
||||||
|
"force_auto_start requires auto_start"
|
||||||
|
);
|
||||||
|
|
||||||
|
let socket_path = Path::new(&args.socket_path);
|
||||||
|
|
||||||
|
if !socket_path.exists() {
|
||||||
|
log::debug!("Mpv socket not found at {}", &args.socket_path);
|
||||||
|
if !args.auto_start {
|
||||||
|
panic!("Mpv socket not found at {}", &args.socket_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
log::debug!("Ensuring parent dir of mpv socket exists");
|
||||||
|
let parent_dir = Path::new(&args.socket_path)
|
||||||
|
.parent()
|
||||||
|
.context("Failed to get parent dir of mpv socket")?;
|
||||||
|
|
||||||
|
if !parent_dir.is_dir() {
|
||||||
|
create_dir_all(parent_dir).context("Failed to create parent dir of mpv socket")?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log::debug!("Existing mpv socket found at {}", &args.socket_path);
|
||||||
|
if args.force_auto_start {
|
||||||
|
log::debug!("Removing mpv socket");
|
||||||
|
std::fs::remove_file(&args.socket_path)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let process_handle = if args.auto_start {
|
||||||
|
log::info!("Starting mpv with socket at {}", &args.socket_path);
|
||||||
|
|
||||||
|
// TODO: try to fetch mpv from PATH
|
||||||
|
Some(
|
||||||
|
Command::new(args.executable_path.as_deref().unwrap_or("mpv"))
|
||||||
|
.arg(format!("--input-ipc-server={}", &args.socket_path))
|
||||||
|
.arg("--idle")
|
||||||
|
.arg("--force-window")
|
||||||
|
// .arg("--fullscreen")
|
||||||
|
// .arg("--no-terminal")
|
||||||
|
// .arg("--load-unsafe-playlists")
|
||||||
|
.arg("--keep-open") // Keep last frame of video on end of video
|
||||||
|
.spawn()
|
||||||
|
.context("Failed to start mpv")?,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
// Wait for mpv to create the socket
|
||||||
|
if tokio::time::timeout(tokio::time::Duration::from_millis(500), async {
|
||||||
|
while !&socket_path.exists() {
|
||||||
|
log::debug!("Waiting for mpv socket at {}", &args.socket_path);
|
||||||
|
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"Failed to connect to mpv socket: {}",
|
||||||
|
&args.socket_path
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
Mpv::connect(&args.socket_path).context(format!(
|
||||||
|
"Failed to connect to mpv socket: {}",
|
||||||
|
&args.socket_path
|
||||||
|
))?,
|
||||||
|
process_handle,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> anyhow::Result<()> {
|
||||||
|
env_logger::init();
|
||||||
|
let args = Args::parse();
|
||||||
|
|
||||||
|
let (mpv, proc) = connect_to_mpv(&MpvConnectionArgs {
|
||||||
|
socket_path: args.mpv_socket_path,
|
||||||
|
executable_path: args.mpv_executable_path,
|
||||||
|
auto_start: args.auto_start_mpv,
|
||||||
|
force_auto_start: args.force_auto_start,
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// TODO: fix address
|
||||||
|
let addr = SocketAddr::from(([127, 0, 0, 1], args.port));
|
||||||
|
log::info!("Starting API on {}", addr);
|
||||||
|
|
||||||
|
let app = Router::new().nest("/api", api::api_routes(mpv));
|
||||||
|
|
||||||
|
if let Some(mut proc) = proc {
|
||||||
|
tokio::select! {
|
||||||
|
exit_status = proc.wait() => {
|
||||||
|
log::warn!("mpv process exited with status: {}", exit_status?);
|
||||||
|
}
|
||||||
|
_ = tokio::signal::ctrl_c() => {
|
||||||
|
log::info!("Received Ctrl-C, exiting");
|
||||||
|
proc.kill().await?;
|
||||||
|
}
|
||||||
|
_ = Server::bind(&addr.clone()).serve(app.into_make_service()) => {
|
||||||
|
log::info!("API server exited");
|
||||||
|
proc.kill().await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tokio::select! {
|
||||||
|
_ = tokio::signal::ctrl_c() => {
|
||||||
|
log::info!("Received Ctrl-C, exiting");
|
||||||
|
}
|
||||||
|
_ = Server::bind(&addr.clone()).serve(app.into_make_service()) => {
|
||||||
|
log::info!("API server exited");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in New Issue