Compare commits

..

15 Commits
v0.1.0 ... main

Author SHA1 Message Date
5a74dd0b02
highlevel_api: fix pause toggling
Some checks failed
Build and test / build (push) Has been cancelled
Build and test / check (push) Has been cancelled
Build and test / test (push) Has been cancelled
Build and test / docs (push) Has been cancelled
2024-12-15 17:37:48 +01:00
ee5aa30335
core: mark MpvDataType as serde(untagged)
Some checks are pending
Build and test / build (push) Waiting to run
Build and test / check (push) Waiting to run
Build and test / test (push) Waiting to run
Build and test / docs (push) Waiting to run
2024-12-15 16:21:25 +01:00
e3297bef15
event_parser: make id optional
Some checks are pending
Build and test / build (push) Waiting to run
Build and test / check (push) Waiting to run
Build and test / test (push) Waiting to run
Build and test / docs (push) Waiting to run
2024-12-15 15:31:01 +01:00
00cae63272
.gitea/build-and-test: remove caching step
Some checks failed
Build and test / build (push) Successful in 1m1s
Build and test / check (push) Successful in 1m4s
Build and test / test (push) Failing after 2m18s
Build and test / docs (push) Successful in 2m55s
This don't seem to be working properly, and it takes a lot of time to
time out. Let's remove it for now
2024-12-14 14:05:23 +01:00
99884b670d
property_parser: fix typo in docstring
All checks were successful
Build and test / build (push) Successful in 10m30s
Build and test / check (push) Successful in 10m36s
Build and test / test (push) Successful in 2m20s
Build and test / docs (push) Successful in 12m5s
2024-12-14 13:52:58 +01:00
3fe7417d4c
core: add docstrings for variants of MpvCommand 2024-12-14 13:52:57 +01:00
eb7277e4fd
treewide: fix type for property change event ids 2024-12-14 13:51:12 +01:00
81479d2f64
MpvError: add copy of command for better context
All checks were successful
Build and test / test (push) Successful in 2m13s
Build and test / build (push) Successful in 10m26s
Build and test / docs (push) Successful in 11m19s
Build and test / check (push) Successful in 10m27s
2024-12-12 16:36:42 +01:00
b2a22a9a57
.gitea/build-and-test: enable cache
All checks were successful
Build and test / build (push) Successful in 10m31s
Build and test / docs (push) Successful in 10m41s
Build and test / check (push) Successful in 10m39s
Build and test / test (push) Successful in 2m17s
2024-12-12 14:47:55 +01:00
ac863c571e
core_api: add Default for Playlist 2024-12-12 14:47:55 +01:00
13397a59f7
tests/integration: increase mpv command timeout 2024-12-12 14:47:55 +01:00
be5c37b433
.gitea/build-and-test: limit test threads 2024-12-12 14:47:55 +01:00
3ca3d7784c
Move repo to Projects, some pipeline updates 2024-12-12 14:47:55 +01:00
fa937567bd
.gitea/build-and-test: switch over to the default builders
All checks were successful
Build and test / build (push) Successful in 10m39s
Build and test / docs (push) Successful in 11m13s
Build and test / check (push) Successful in 10m38s
Build and test / test (push) Successful in 16m13s
2024-12-09 23:59:59 +01:00
c129e5104d
.gitea/workflows: adjust rsync action url
All checks were successful
Build and test / build (push) Successful in 1m54s
Build and test / check (push) Successful in 1m56s
Build and test / test (push) Successful in 3m25s
Build and test / docs (push) Successful in 2m26s
2024-08-21 07:19:24 +02:00
15 changed files with 143 additions and 121 deletions

View File

@ -7,37 +7,26 @@ on:
jobs:
build:
runs-on: ubuntu-latest-personal
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install latest nightly toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
override: true
- name: Cache dependencies
uses: Swatinem/rust-cache@v2
- name: Install rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Build
run: cargo build --all-features --verbose --release
check:
runs-on: ubuntu-latest-personal
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install latest nightly toolchain
uses: actions-rs/toolchain@v1
- name: Install rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
toolchain: nightly
override: true
components: rustfmt, clippy
- name: Cache dependencies
uses: Swatinem/rust-cache@v2
- name: Check code format
run: cargo fmt -- --check
@ -45,30 +34,27 @@ jobs:
run: cargo clippy --all-features -- --deny warnings
test:
runs-on: ubuntu-latest-personal
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: cargo-bins/cargo-binstall@main
- uses: actions/checkout@v4
- name: Install cargo binstall
uses: cargo-bins/cargo-binstall@main
- name: Install mpv
run: apt-get update && apt-get install -y mpv
- name: Install latest nightly toolchain
uses: actions-rs/toolchain@v1
- name: Install rust toolchain
uses: dtolnay/rust-toolchain@nightly
with:
toolchain: nightly
override: true
components: llvm-tools-preview
- name: Cache dependencies
uses: Swatinem/rust-cache@v2
- name: Install nextest
run: cargo binstall -y cargo-nextest --secure
- name: Run tests
run: |
cargo nextest run --all-features --release --no-fail-fast
cargo nextest run --all-features --release --no-fail-fast --test-threads=1
env:
RUST_LOG: "trace"
RUSTFLAGS: "-Cinstrument-coverage"
@ -96,38 +82,32 @@ jobs:
target/coverage/
- name: Upload test report
uses: https://git.pvv.ntnu.no/oysteikt/rsync-action@main
uses: https://git.pvv.ntnu.no/Projects/rsync-action@v1
with:
source: target/coverage/html/
target: mpvipc-async/${{ gitea.ref_name }}/coverage/
username: oysteikt
ssh-key: ${{ secrets.OYSTEIKT_GITEA_WEBDOCS_SSH_KEY }}
host: microbel.pvv.ntnu.no
known-hosts: "microbel.pvv.ntnu.no ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEq0yasKP0mH6PI6ypmuzPzMnbHELo9k+YB5yW534aKudKZS65YsHJKQ9vapOtmegrn5MQbCCgrshf+/XwZcjbM="
target: ${{ gitea.ref_name }}/coverage/
username: gitea-web
ssh-key: ${{ secrets.WEB_SYNC_SSH_KEY }}
host: bekkalokk.pvv.ntnu.no
known-hosts: "bekkalokk.pvv.ntnu.no ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEI6VSaDrMG8+flg4/AeHlAFIen8RUzWh6URQKqFegSx"
docs:
runs-on: ubuntu-latest-personal
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install latest nightly toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
override: true
- name: Cache dependencies
uses: Swatinem/rust-cache@v2
- name: Install rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Build docs
run: cargo doc --all-features --document-private-items --release
- name: Transfer files
uses: https://git.pvv.ntnu.no/oysteikt/rsync-action@main
uses: https://git.pvv.ntnu.no/Projects/rsync-action@v1
with:
source: target/doc/
target: mpvipc-async/${{ gitea.ref_name }}/docs/
username: oysteikt
ssh-key: ${{ secrets.OYSTEIKT_GITEA_WEBDOCS_SSH_KEY }}
host: microbel.pvv.ntnu.no
known-hosts: "microbel.pvv.ntnu.no ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEq0yasKP0mH6PI6ypmuzPzMnbHELo9k+YB5yW534aKudKZS65YsHJKQ9vapOtmegrn5MQbCCgrshf+/XwZcjbM="
target: ${{ gitea.ref_name }}/docs/
username: gitea-web
ssh-key: ${{ secrets.WEB_SYNC_SSH_KEY }}
host: bekkalokk.pvv.ntnu.no
known-hosts: "bekkalokk.pvv.ntnu.no ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEI6VSaDrMG8+flg4/AeHlAFIen8RUzWh6URQKqFegSx"

View File

@ -7,9 +7,8 @@ authors = [
]
description = "A small library which provides bindings to control existing mpv instances through sockets."
license = "GPL-3.0"
homepage = "https://git.pvv.ntnu.no/oysteikt/mpvipc-async"
repository = "https://git.pvv.ntnu.no/oysteikt/mpvipc-async"
documentation = "https://pvv.ntnu.no/~oysteikt/gitea/mpvipc-async/master/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"

View File

@ -1,11 +1,10 @@
[![Coverage](https://pvv.ntnu.no/~oysteikt/gitea/mpvipc-async/main/coverage/badges/for_the_badge.svg)](https://pvv.ntnu.no/~oysteikt/gitea/mpvipc-async/main/coverage/src/)
[![Docs](https://img.shields.io/badge/docs-blue?style=for-the-badge&logo=rust)](https://pvv.ntnu.no/~oysteikt/gitea/mpvipc-async/main/docs/mpvipc_async/)
[![Coverage](https://pages.pvv.ntnu.no/Projects/mpvipc-async/main/coverage/badges/for_the_badge.svg)](https://pages.pvv.ntnu.no/Projects/mpvipc-async/main/coverage/src/)
[![Docs](https://img.shields.io/badge/docs-blue?style=for-the-badge&logo=rust)](https://pages.pvv.ntnu.no/Projects/mpvipc-async/main/docs/mpvipc_async/)
# mpvipc-async
> **NOTE:** This is a fork of [gitlab.com/mpv-ipc/mpvipc](https://gitlab.com/mpv-ipc/mpvipc), which introduces a lot of changes to be able to use the library asynchronously with [tokio](https://github.com/tokio-rs/tokio).
---
A small library which provides bindings to control existing mpv instances through sockets.

View File

@ -27,39 +27,65 @@ use crate::{
/// the upstream list of commands.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MpvCommand {
/// Load the given file or URL and play it.
LoadFile {
file: String,
option: PlaylistAddOptions,
},
/// Load the given playlist file or URL.
LoadList {
file: String,
option: PlaylistAddOptions,
},
/// Clear the playlist, except for the currently playing file.
PlaylistClear,
PlaylistMove {
from: usize,
to: usize,
},
Observe {
id: usize,
property: String,
},
///Move the playlist entry at `from`, so that it takes the place of the entry `to`.
/// (Paradoxically, the moved playlist entry will not have the index value `to` after moving
/// if `from` was lower than `to`, because `to` refers to the target entry, not the index
/// the entry will have after moving.)
PlaylistMove { from: usize, to: usize },
/// Observe a property. This will start triggering [`Event::PropertyChange`] events
/// in the event stream whenever the specific property changes.
/// You can use [`Mpv::get_event_stream`] to get the stream.
Observe { id: u64, property: String },
/// Skip to the next entry in the playlist.
PlaylistNext,
/// Skip to the previous entry in the playlist.
PlaylistPrev,
/// Remove an entry from the playlist by its position in the playlist.
PlaylistRemove(usize),
/// Shuffle the playlist
PlaylistShuffle,
/// Exit the player
Quit,
/// Send a message to all clients, and pass it the following list of arguments.
/// What this message means, how many arguments it takes, and what the arguments
/// mean is fully up to the receiver and the sender.
ScriptMessage(Vec<String>),
ScriptMessageTo {
target: String,
args: Vec<String>,
},
Seek {
seconds: f64,
option: SeekOptions,
},
/// Same as [`MpvCommand::ScriptMessage`], but send the message to a specific target.
ScriptMessageTo { target: String, args: Vec<String> },
/// Change the playback position.
Seek { seconds: f64, option: SeekOptions },
/// Stop the current playback, and clear the playlist.
/// This esentially resets the entire player state without exiting mpv.
Stop,
Unobserve(usize),
/// Unobserve all properties registered with the given id.
/// See [`MpvCommand::Observe`] for more context.
Unobserve(u64),
}
/// Helper trait to keep track of the string literals that mpv expects.
@ -69,6 +95,7 @@ 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),
@ -82,7 +109,7 @@ pub enum MpvDataType {
}
/// A mpv playlist.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct Playlist(pub Vec<PlaylistEntry>);
/// A single entry in the mpv playlist.

View File

@ -8,8 +8,11 @@ use crate::{MpvDataType, Property};
/// Any error that can occur when interacting with mpv.
#[derive(Error, Debug)]
pub enum MpvError {
#[error("MpvError: {0}")]
MpvError(String),
#[error("Mpv returned error in response to command: {message}\nCommand: {command:#?}")]
MpvError {
command: Vec<Value>,
message: String,
},
#[error("Error communicating over mpv socket: {0}")]
MpvSocketConnectionError(String),
@ -53,7 +56,16 @@ pub enum MpvError {
impl PartialEq for MpvError {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::MpvError(l0), Self::MpvError(r0)) => l0 == r0,
(
Self::MpvError {
command: l_command,
message: l_message,
},
Self::MpvError {
command: r_command,
message: r_message,
},
) => l_command == r_command && l_message == r_message,
(Self::MpvSocketConnectionError(l0), Self::MpvSocketConnectionError(r0)) => l0 == r0,
(Self::InternalConnectionError(l0), Self::InternalConnectionError(r0)) => l0 == r0,
(Self::JsonParseError(l0), Self::JsonParseError(r0)) => {

View File

@ -109,7 +109,7 @@ pub enum Event {
VideoReconfig,
AudioReconfig,
PropertyChange {
id: usize,
id: Option<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_key_as!(as_u64, "id", event) as usize;
let id = get_optional_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()?;

View File

@ -88,11 +88,11 @@ pub trait MpvExt {
/// Notify mpv to send events whenever a property changes.
/// See [`Mpv::get_event_stream`] and [`Property`](crate::Property) for more information.
async fn observe_property(&self, id: usize, property: &str) -> Result<(), MpvError>;
async fn observe_property(&self, id: u64, property: &str) -> Result<(), MpvError>;
/// Stop observing a property.
/// See [`Mpv::get_event_stream`] and [`Property`](crate::Property) for more information.
async fn unobserve_property(&self, id: usize) -> Result<(), MpvError>;
async fn unobserve_property(&self, id: u64) -> Result<(), MpvError>;
/// Skip to the next entry in the playlist.
async fn next(&self) -> Result<(), MpvError>;
@ -259,7 +259,7 @@ impl MpvExt for Mpv {
self.run_command(MpvCommand::PlaylistPrev).await
}
async fn observe_property(&self, id: usize, property: &str) -> Result<(), MpvError> {
async fn observe_property(&self, id: u64, property: &str) -> Result<(), MpvError> {
self.run_command(MpvCommand::Observe {
id,
property: property.to_string(),
@ -267,7 +267,7 @@ impl MpvExt for Mpv {
.await
}
async fn unobserve_property(&self, id: usize) -> Result<(), MpvError> {
async fn unobserve_property(&self, id: u64) -> Result<(), MpvError> {
self.run_command(MpvCommand::Unobserve(id)).await
}
@ -323,9 +323,9 @@ impl MpvExt for Mpv {
Switch::Off => "yes",
Switch::Toggle => {
if self.is_playing().await? {
"no"
} else {
"yes"
} else {
"no"
}
}
};

View File

@ -24,8 +24,8 @@ pub(crate) enum MpvIpcCommand {
Command(Vec<String>),
GetProperty(String),
SetProperty(String, Value),
ObserveProperty(usize, String),
UnobserveProperty(usize),
ObserveProperty(u64, String),
UnobserveProperty(u64),
Exit,
}
@ -92,7 +92,7 @@ impl MpvIpc {
log::trace!("Received response: {:?}", response);
parse_mpv_response_data(response?)
parse_mpv_response_data(response?, command)
}
pub(crate) async fn get_mpv_property(
@ -114,17 +114,14 @@ impl MpvIpc {
pub(crate) async fn observe_property(
&mut self,
id: usize,
id: u64,
property: &str,
) -> Result<Option<Value>, MpvError> {
self.send_command(&[json!("observe_property"), json!(id), json!(property)])
.await
}
pub(crate) async fn unobserve_property(
&mut self,
id: usize,
) -> Result<Option<Value>, MpvError> {
pub(crate) async fn unobserve_property(&mut self, id: u64) -> Result<Option<Value>, MpvError> {
self.send_command(&[json!("unobserve_property"), json!(id)])
.await
}
@ -197,7 +194,7 @@ impl MpvIpc {
/// This function does the most basic JSON parsing and error handling
/// for status codes and errors that all responses from mpv are
/// expected to contain.
fn parse_mpv_response_data(value: Value) -> Result<Option<Value>, MpvError> {
fn parse_mpv_response_data(value: Value, command: &[Value]) -> Result<Option<Value>, MpvError> {
log::trace!("Parsing mpv response data: {:?}", value);
let result = value
.as_object()
@ -225,7 +222,10 @@ fn parse_mpv_response_data(value: Value) -> Result<Option<Value>, MpvError> {
.and_then(|(error, data)| match error {
"success" => Ok(data),
"property unavailable" => Ok(None),
err => Err(MpvError::MpvError(err.to_string())),
err => Err(MpvError::MpvError {
command: command.to_owned(),
message: err.to_string(),
}),
});
match &result {

View File

@ -1,5 +1,5 @@
//! JSON parsing logic for properties returned by
//! [[`Event::PropertyChange`], and used internally in `MpvExt`
//! [`Event::PropertyChange`], and used internally in `MpvExt`
//! to parse the response from `Mpv::get_property()`.
//!
//! This module is used to parse the json data from the `data` field of

View File

@ -8,7 +8,7 @@ use test_log::test;
use super::*;
const MPV_CHANNEL_ID: usize = 1337;
const MPV_CHANNEL_ID: u64 = 1337;
#[derive(Error, Debug)]
enum PropertyCheckingThreadError {
@ -43,7 +43,7 @@ where
match event {
Some(Ok(event)) => {
match event {
Event::PropertyChange { id: MPV_CHANNEL_ID, name, data } => {
Event::PropertyChange { id: Some(MPV_CHANNEL_ID), name, data } => {
let property = parse_property(&name, data).unwrap();
if !on_property(property.clone()) {
return Err(PropertyCheckingThreadError::UnexpectedPropertyError(property))

View File

@ -47,10 +47,13 @@ async fn test_get_unavailable_property() -> Result<(), MpvError> {
async fn test_get_nonexistent_property() -> Result<(), MpvError> {
let (mut proc, mpv) = spawn_headless_mpv().await.unwrap();
let nonexistent = mpv.get_property::<f64>("nonexistent").await;
assert_eq!(
nonexistent,
Err(MpvError::MpvError("property not found".to_string()))
);
match nonexistent {
Err(MpvError::MpvError { message, .. }) => {
assert_eq!(message, "property not found");
}
_ => panic!("Unexpected result: {:?}", nonexistent),
}
mpv.kill().await.unwrap();
proc.kill().await.unwrap();

View File

@ -25,7 +25,7 @@ pub async fn spawn_headless_mpv() -> Result<(Child, Mpv), MpvError> {
.spawn()
.expect("Failed to start mpv");
timeout(Duration::from_millis(500), async {
timeout(Duration::from_millis(1000), async {
while !&socket_path.exists() {
sleep(Duration::from_millis(10)).await;
}

View File

@ -52,7 +52,7 @@ async fn test_observe_event_successful() {
assert_eq!(
event,
Event::PropertyChange {
id: 1,
id: Some(1),
name: "volume".to_string(),
data: Some(MpvDataType::Double(64.0))
}

View File

@ -151,8 +151,8 @@ async fn test_get_property_simultaneous_requests() {
tokio::time::sleep(Duration::from_millis(2)).await;
let maybe_volume = mpv_clone_3.get_property::<f64>("nonexistent").await;
match maybe_volume {
Err(MpvError::MpvError(err)) => {
assert_eq!(err, "property not found");
Err(MpvError::MpvError { message, .. }) => {
assert_eq!(message, "property not found");
}
_ => panic!("Unexpected result: {:?}", maybe_volume),
}

View File

@ -63,12 +63,12 @@ async fn test_set_property_wrong_type() -> Result<(), MpvError> {
let mpv = Mpv::connect_socket(server).await?;
let maybe_volume = mpv.set_property::<bool>("volume", true).await;
assert_eq!(
maybe_volume,
Err(MpvError::MpvError(
"unsupported format for accessing property".to_string()
))
);
match maybe_volume {
Err(MpvError::MpvError { message, .. }) => {
assert_eq!(message, "unsupported format for accessing property");
}
_ => panic!("Unexpected result: {:?}", maybe_volume),
}
join_handle.await.unwrap().unwrap();
@ -84,10 +84,12 @@ async fn test_get_property_error() -> Result<(), MpvError> {
let mpv = Mpv::connect_socket(server).await?;
let maybe_volume = mpv.set_property("nonexistent", true).await;
assert_eq!(
maybe_volume,
Err(MpvError::MpvError("property not found".to_string()))
);
match maybe_volume {
Err(MpvError::MpvError { message, .. }) => {
assert_eq!(message, "property not found");
}
_ => panic!("Unexpected result: {:?}", maybe_volume),
}
join_handle.await.unwrap().unwrap();
@ -161,8 +163,8 @@ async fn test_set_property_simultaneous_requests() {
tokio::time::sleep(Duration::from_millis(2)).await;
let maybe_volume = mpv_clone_3.set_property("nonexistent", "a").await;
match maybe_volume {
Err(MpvError::MpvError(err)) => {
assert_eq!(err, "property not found");
Err(MpvError::MpvError { message, .. }) => {
assert_eq!(message, "property not found");
}
_ => panic!("Unexpected result: {:?}", maybe_volume),
}