Compare commits
4 Commits
main
...
test-highl
| Author | SHA1 | Date | |
|---|---|---|---|
|
633fd4b41c
|
|||
|
77d4e80eec
|
|||
|
44d7e15fb1
|
|||
|
c985b696ec
|
@@ -7,9 +7,9 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: debian-latest
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
@@ -18,9 +18,9 @@ jobs:
|
||||
run: cargo build --all-features --verbose --release
|
||||
|
||||
check:
|
||||
runs-on: debian-latest
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
@@ -34,9 +34,9 @@ jobs:
|
||||
run: cargo clippy --all-features -- --deny warnings
|
||||
|
||||
test:
|
||||
runs-on: debian-latest
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install cargo binstall
|
||||
uses: cargo-bins/cargo-binstall@main
|
||||
@@ -88,13 +88,13 @@ jobs:
|
||||
target: ${{ gitea.ref_name }}/coverage/
|
||||
username: gitea-web
|
||||
ssh-key: ${{ secrets.WEB_SYNC_SSH_KEY }}
|
||||
host: pages.pvv.ntnu.no
|
||||
known-hosts: "pages.pvv.ntnu.no ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH2QjfFB+city1SYqltkVqWACfo1j37k+oQQfj13mtgg"
|
||||
host: bekkalokk.pvv.ntnu.no
|
||||
known-hosts: "bekkalokk.pvv.ntnu.no ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEI6VSaDrMG8+flg4/AeHlAFIen8RUzWh6URQKqFegSx"
|
||||
|
||||
docs:
|
||||
runs-on: debian-latest
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
@@ -109,5 +109,5 @@ jobs:
|
||||
target: ${{ gitea.ref_name }}/docs/
|
||||
username: gitea-web
|
||||
ssh-key: ${{ secrets.WEB_SYNC_SSH_KEY }}
|
||||
host: pages.pvv.ntnu.no
|
||||
known-hosts: "pages.pvv.ntnu.no ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH2QjfFB+city1SYqltkVqWACfo1j37k+oQQfj13mtgg"
|
||||
host: bekkalokk.pvv.ntnu.no
|
||||
known-hosts: "bekkalokk.pvv.ntnu.no ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEI6VSaDrMG8+flg4/AeHlAFIen8RUzWh6URQKqFegSx"
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,2 +1,5 @@
|
||||
target
|
||||
Cargo.lock
|
||||
|
||||
test_assets/*
|
||||
!test_assets/.gitkeep
|
||||
28
Cargo.toml
28
Cargo.toml
@@ -7,26 +7,26 @@ authors = [
|
||||
]
|
||||
description = "A small library which provides bindings to control existing mpv instances through sockets."
|
||||
license = "GPL-3.0"
|
||||
repository = "https://git.pvv.ntnu.no/Grzegorz/mpvipc-async"
|
||||
documentation = "https://pages.pvv.ntnu.no/Grzegorz/mpvipc-async/main/docs/mpvipc_async/"
|
||||
repository = "https://git.pvv.ntnu.no/Projects/mpvipc-async"
|
||||
documentation = "https://pages.pvv.ntnu.no/Projects/mpvipc-async/main/docs/mpvipc_async/"
|
||||
edition = "2021"
|
||||
rust-version = "1.75"
|
||||
|
||||
[dependencies]
|
||||
serde_json = "1.0.145"
|
||||
log = "0.4.29"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
tokio = { version = "1.48.0", features = ["sync", "macros", "rt", "net"] }
|
||||
tokio-util = { version = "0.7.17", features = ["codec"] }
|
||||
futures = "0.3.31"
|
||||
tokio-stream = { version = "0.1.17", features = ["sync"] }
|
||||
thiserror = "2.0.17"
|
||||
serde_json = "1.0.104"
|
||||
log = "0.4.19"
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
tokio = { version = "1.37.0", features = ["sync", "macros", "rt", "net"] }
|
||||
tokio-util = { version = "0.7.10", features = ["codec"] }
|
||||
futures = "0.3.30"
|
||||
tokio-stream = { version = "0.1.15", features = ["sync"] }
|
||||
thiserror = "1.0.59"
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.11.8"
|
||||
test-log = "0.2.19"
|
||||
tokio = { version = "1.48.0", features = ["rt-multi-thread", "time", "process"] }
|
||||
uuid = { version = "1.19.0", features = ["v4"] }
|
||||
env_logger = "0.10.0"
|
||||
test-log = "0.2.15"
|
||||
tokio = { version = "1.37.0", features = ["rt-multi-thread", "time", "process"] }
|
||||
uuid = { version = "1.8.0", features = ["v4"] }
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[](https://pages.pvv.ntnu.no/Grzegorz/mpvipc-async/main/coverage/src/)
|
||||
[](https://pages.pvv.ntnu.no/Grzegorz/mpvipc-async/main/docs/mpvipc_async/)
|
||||
[](https://pages.pvv.ntnu.no/Projects/mpvipc-async/main/coverage/src/)
|
||||
[](https://pages.pvv.ntnu.no/Projects/mpvipc-async/main/docs/mpvipc_async/)
|
||||
|
||||
# mpvipc-async
|
||||
|
||||
|
||||
18
flake.lock
generated
18
flake.lock
generated
@@ -8,11 +8,11 @@
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1764830916,
|
||||
"narHash": "sha256-GCw5hPDoA2gXqRh6ThWxcXzA+DqqRR7DaLfPx69SVEg=",
|
||||
"lastModified": 1713421495,
|
||||
"narHash": "sha256-5vVF9W1tJT+WdfpWAEG76KywktKDAW/71mVmNHEHjac=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "ea1d6932e79aa6931c932c83957f6547064759e2",
|
||||
"rev": "fd47b1f9404fae02a4f38bd9f4b12bad7833c96b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -23,11 +23,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1764667669,
|
||||
"narHash": "sha256-7WUCZfmqLAssbDqwg9cUDAXrSoXN79eEEq17qhTNM/Y=",
|
||||
"lastModified": 1713248628,
|
||||
"narHash": "sha256-NLznXB5AOnniUtZsyy/aPWOk8ussTuePp2acb9U+ISA=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "418468ac9527e799809c900eda37cbff999199b6",
|
||||
"rev": "5672bc9dbf9d88246ddab5ac454e82318d094bb8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -46,11 +46,11 @@
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1764778537,
|
||||
"narHash": "sha256-SNL+Fj1ZWiBqCrHJT1S9vMZujrWxCOmf3zkT66XSnhE=",
|
||||
"lastModified": 1713373173,
|
||||
"narHash": "sha256-octd9BFY9G/Gbr4KfwK4itZp4Lx+qvJeRRcYnN+dEH8=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "633cff25206d5108043d87617a43c9d04aa42c88",
|
||||
"rev": "46702ffc1a02a2ac153f1d1ce619ec917af8f3a6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
pkgs.mpv
|
||||
pkgs.grcov
|
||||
pkgs.cargo-nextest
|
||||
pkgs.cargo-edit
|
||||
pkgs.ffmpeg
|
||||
];
|
||||
RUST_SRC_PATH = "${toolchain.rust-src}/lib/rustlib/src/rust/library";
|
||||
});
|
||||
|
||||
21
setup_test_assets.sh
Executable file
21
setup_test_assets.sh
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
REQUIRED_COMMANDS=(
|
||||
"git"
|
||||
"ffmpeg"
|
||||
)
|
||||
|
||||
for cmd in "${REQUIRED_COMMANDS[@]}"; do
|
||||
if ! command -v "$cmd" &> /dev/null; then
|
||||
echo "Command '$cmd' not found. Please install it and try again."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
ROOT_DIR=$(git rev-parse --show-toplevel)
|
||||
|
||||
# Generate 30 seconds of 480p video with black background
|
||||
|
||||
ffmpeg -f lavfi -i color=c=black:s=640x480:d=30 -c:v libx264 -t 30 -pix_fmt yuv420p "$ROOT_DIR/test_assets/black-background-30s-480p.mp4"
|
||||
@@ -95,7 +95,6 @@ pub(crate) trait IntoRawCommandPart {
|
||||
|
||||
/// Generic data type representing all possible data types that mpv can return.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum MpvDataType {
|
||||
Array(Vec<MpvDataType>),
|
||||
Bool(bool),
|
||||
|
||||
@@ -109,7 +109,7 @@ pub enum Event {
|
||||
VideoReconfig,
|
||||
AudioReconfig,
|
||||
PropertyChange {
|
||||
id: Option<u64>,
|
||||
id: u64,
|
||||
name: String,
|
||||
data: Option<MpvDataType>,
|
||||
},
|
||||
@@ -296,7 +296,7 @@ fn parse_client_message(event: &Map<String, Value>) -> Result<Event, MpvError> {
|
||||
}
|
||||
|
||||
fn parse_property_change(event: &Map<String, Value>) -> Result<Event, MpvError> {
|
||||
let id = get_optional_key_as!(as_u64, "id", event);
|
||||
let id = get_key_as!(as_u64, "id", event);
|
||||
let property_name = get_key_as!(as_str, "name", event);
|
||||
let data = event.get("data").map(json_to_value).transpose()?;
|
||||
|
||||
|
||||
@@ -323,9 +323,9 @@ impl MpvExt for Mpv {
|
||||
Switch::Off => "yes",
|
||||
Switch::Toggle => {
|
||||
if self.is_playing().await? {
|
||||
"yes"
|
||||
} else {
|
||||
"no"
|
||||
} else {
|
||||
"yes"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
0
test_assets/.gitkeep
Normal file
0
test_assets/.gitkeep
Normal file
Binary file not shown.
@@ -43,7 +43,7 @@ where
|
||||
match event {
|
||||
Some(Ok(event)) => {
|
||||
match event {
|
||||
Event::PropertyChange { id: Some(MPV_CHANNEL_ID), name, data } => {
|
||||
Event::PropertyChange { id: MPV_CHANNEL_ID, name, data } => {
|
||||
let property = parse_property(&name, data).unwrap();
|
||||
if !on_property(property.clone()) {
|
||||
return Err(PropertyCheckingThreadError::UnexpectedPropertyError(property))
|
||||
@@ -115,7 +115,7 @@ async fn graceful_shutdown(
|
||||
#[test(tokio::test)]
|
||||
#[cfg(target_family = "unix")]
|
||||
async fn test_highlevel_event_pause() -> Result<(), MpvError> {
|
||||
let (proc, mpv) = spawn_headless_mpv().await?;
|
||||
let (proc, mpv) = spawn_mpv(true).await?;
|
||||
|
||||
mpv.observe_property(MPV_CHANNEL_ID, "pause").await?;
|
||||
|
||||
@@ -144,7 +144,7 @@ async fn test_highlevel_event_pause() -> Result<(), MpvError> {
|
||||
#[test(tokio::test)]
|
||||
#[cfg(target_family = "unix")]
|
||||
async fn test_highlevel_event_volume() -> Result<(), MpvError> {
|
||||
let (proc, mpv) = spawn_headless_mpv().await?;
|
||||
let (proc, mpv) = spawn_mpv(true).await?;
|
||||
|
||||
mpv.observe_property(1337, "volume").await?;
|
||||
let events = mpv.get_event_stream().await;
|
||||
@@ -174,7 +174,7 @@ async fn test_highlevel_event_volume() -> Result<(), MpvError> {
|
||||
#[test(tokio::test)]
|
||||
#[cfg(target_family = "unix")]
|
||||
async fn test_highlevel_event_mute() -> Result<(), MpvError> {
|
||||
let (proc, mpv) = spawn_headless_mpv().await?;
|
||||
let (proc, mpv) = spawn_mpv(true).await?;
|
||||
|
||||
mpv.observe_property(1337, "mute").await?;
|
||||
let events = mpv.get_event_stream().await;
|
||||
@@ -202,7 +202,7 @@ async fn test_highlevel_event_mute() -> Result<(), MpvError> {
|
||||
#[test(tokio::test)]
|
||||
#[cfg(target_family = "unix")]
|
||||
async fn test_highlevel_event_duration() -> Result<(), MpvError> {
|
||||
let (proc, mpv) = spawn_headless_mpv().await?;
|
||||
let (proc, mpv) = spawn_mpv(true).await?;
|
||||
|
||||
mpv.observe_property(1337, "duration").await?;
|
||||
|
||||
|
||||
35
tests/integration_tests/highlevel_api.rs
Normal file
35
tests/integration_tests/highlevel_api.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use super::util::{get_test_asset, spawn_mpv};
|
||||
|
||||
use mpvipc_async::{
|
||||
MpvError, MpvExt, PlaylistAddOptions, PlaylistAddTypeOptions, SeekOptions, Switch,
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg(target_family = "unix")]
|
||||
async fn test_seek() -> Result<(), MpvError> {
|
||||
let (mut proc, mpv) = spawn_mpv(false).await.unwrap();
|
||||
mpv.playlist_add(
|
||||
&get_test_asset("black-background-30s-480p.mp4"),
|
||||
PlaylistAddTypeOptions::File,
|
||||
PlaylistAddOptions::Append,
|
||||
)
|
||||
.await?;
|
||||
|
||||
mpv.set_playback(Switch::On).await?;
|
||||
mpv.set_playback(Switch::Off).await?;
|
||||
|
||||
// TODO: wait for property "seekable" to be true
|
||||
|
||||
mpv.seek(10.0, SeekOptions::Relative).await?;
|
||||
let time_pos: f64 = mpv.get_property("time-pos").await?.unwrap();
|
||||
assert_eq!(time_pos, 10.0);
|
||||
|
||||
mpv.seek(5.0, SeekOptions::Relative).await?;
|
||||
let time_pos: f64 = mpv.get_property("time-pos").await?.unwrap();
|
||||
assert_eq!(time_pos, 15.0);
|
||||
|
||||
mpv.kill().await.unwrap();
|
||||
proc.kill().await.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -5,7 +5,7 @@ use super::*;
|
||||
#[tokio::test]
|
||||
#[cfg(target_family = "unix")]
|
||||
async fn test_get_mpv_version() -> Result<(), MpvError> {
|
||||
let (mut proc, mpv) = spawn_headless_mpv().await.unwrap();
|
||||
let (mut proc, mpv) = spawn_mpv(true).await.unwrap();
|
||||
let version: String = mpv.get_property("mpv-version").await?.unwrap();
|
||||
assert!(version.starts_with("mpv"));
|
||||
|
||||
@@ -18,7 +18,7 @@ async fn test_get_mpv_version() -> Result<(), MpvError> {
|
||||
#[tokio::test]
|
||||
#[cfg(target_family = "unix")]
|
||||
async fn test_set_property() -> Result<(), MpvError> {
|
||||
let (mut proc, mpv) = spawn_headless_mpv().await.unwrap();
|
||||
let (mut proc, mpv) = spawn_mpv(true).await.unwrap();
|
||||
mpv.set_property("pause", true).await.unwrap();
|
||||
let paused: bool = mpv.get_property("pause").await?.unwrap();
|
||||
assert!(paused);
|
||||
@@ -32,7 +32,7 @@ async fn test_set_property() -> Result<(), MpvError> {
|
||||
#[tokio::test]
|
||||
#[cfg(target_family = "unix")]
|
||||
async fn test_get_unavailable_property() -> Result<(), MpvError> {
|
||||
let (mut proc, mpv) = spawn_headless_mpv().await.unwrap();
|
||||
let (mut proc, mpv) = spawn_mpv(true).await.unwrap();
|
||||
let time_pos = mpv.get_property::<f64>("time-pos").await;
|
||||
assert_eq!(time_pos, Ok(None));
|
||||
|
||||
@@ -45,7 +45,7 @@ async fn test_get_unavailable_property() -> Result<(), MpvError> {
|
||||
#[tokio::test]
|
||||
#[cfg(target_family = "unix")]
|
||||
async fn test_get_nonexistent_property() -> Result<(), MpvError> {
|
||||
let (mut proc, mpv) = spawn_headless_mpv().await.unwrap();
|
||||
let (mut proc, mpv) = spawn_mpv(true).await.unwrap();
|
||||
let nonexistent = mpv.get_property::<f64>("nonexistent").await;
|
||||
|
||||
match nonexistent {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
mod event_property_parser;
|
||||
mod highlevel_api;
|
||||
mod misc;
|
||||
mod util;
|
||||
|
||||
|
||||
@@ -6,8 +6,35 @@ use tokio::{
|
||||
time::{sleep, timeout},
|
||||
};
|
||||
|
||||
pub fn assert_test_assets_exist() {
|
||||
let test_data_dir = Path::new("test_assets");
|
||||
if !test_data_dir.exists()
|
||||
|| !test_data_dir.is_dir()
|
||||
// `.gitkeep` should always be present, so there should be at least 2 entries
|
||||
|| test_data_dir.read_dir().unwrap().count() <= 1
|
||||
{
|
||||
panic!(
|
||||
"Test assets directory not found at {:?}, please run `./setup_test_assets.sh`",
|
||||
test_data_dir
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_test_assets_dir() -> &'static Path {
|
||||
Path::new("test_assets")
|
||||
}
|
||||
|
||||
pub fn get_test_asset(file_name: &str) -> String {
|
||||
assert_test_assets_exist();
|
||||
|
||||
let test_assets_dir = get_test_assets_dir();
|
||||
let file_path = test_assets_dir.join(file_name);
|
||||
file_path.to_str().unwrap().to_string()
|
||||
}
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
pub async fn spawn_headless_mpv() -> Result<(Child, Mpv), MpvError> {
|
||||
pub async fn spawn_mpv(headless: bool) -> Result<(Child, Mpv), MpvError> {
|
||||
let socket_path_str = format!("/tmp/mpv-ipc-{}", uuid::Uuid::new_v4());
|
||||
let socket_path = Path::new(&socket_path_str);
|
||||
|
||||
@@ -15,8 +42,11 @@ pub async fn spawn_headless_mpv() -> Result<(Child, Mpv), MpvError> {
|
||||
let process_handle = Command::new("mpv")
|
||||
.arg("--no-config")
|
||||
.arg("--idle")
|
||||
.arg("--no-video")
|
||||
.arg("--no-audio")
|
||||
.args(if headless {
|
||||
vec!["--no-video", "--no-audio"]
|
||||
} else {
|
||||
vec![]
|
||||
})
|
||||
.arg(format!(
|
||||
"--input-ipc-server={}",
|
||||
&socket_path.to_str().unwrap()
|
||||
|
||||
@@ -52,7 +52,7 @@ async fn test_observe_event_successful() {
|
||||
assert_eq!(
|
||||
event,
|
||||
Event::PropertyChange {
|
||||
id: Some(1),
|
||||
id: 1,
|
||||
name: "volume".to_string(),
|
||||
data: Some(MpvDataType::Double(64.0))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user