Adjust to latest v5 changes and fix minor mistakes

v5-api
Dominik Nakamura 2 years ago
parent 825315b0bf
commit 3093734a4e
No known key found for this signature in database
GPG Key ID: E4C6A749B2491910

@ -44,21 +44,21 @@ impl<'a> General<'a> {
/// A vendor is a unique name registered by a third-party plugin or script, which allows for
/// custom requests and events to be added to obs-websocket. If a plugin or script implements
/// vendor requests or events, documentation is expected to be provided with them.
pub async fn call_vendor_request<T, R>(&self, request: CallVendorRequest<'_, T>) -> Result<R>
pub async fn call_vendor_request<T, R>(
&self,
request: CallVendorRequest<'_, T>,
) -> Result<responses::VendorResponse<R>>
where
T: Serialize,
R: DeserializeOwned,
{
self.client
.send_message::<_, responses::CallVendorResponse<R>>(Request::CallVendorRequest(
CallVendorRequestInternal {
vendor_name: request.vendor_name,
request_type: request.request_type,
request_data: serde_json::to_value(&request.request_data)
.map_err(Error::SerializeCustomData)?,
},
))
.send_message(Request::CallVendorRequest(CallVendorRequestInternal {
vendor_name: request.vendor_name,
request_type: request.request_type,
request_data: serde_json::to_value(&request.request_data)
.map_err(Error::SerializeCustomData)?,
}))
.await
.map(|cvr| cvr.response_data)
}
}

@ -30,10 +30,10 @@ use tracing::{debug, error, info, trace};
use self::connection::{ReceiverList, ReidentifyReceiverList};
pub use self::{
config::Config, connection::HandshakeError, filters::Filters, general::General,
hotkeys::Hotkeys, inputs::Inputs, media_inputs::MediaInputs, profiles::Profiles,
recording::Recording, replay_buffer::ReplayBuffer, scene_collections::SceneCollections,
scene_items::SceneItems, scenes::Scenes, sources::Sources, streaming::Streaming,
transitions::Transitions, ui::Ui, virtual_cam::VirtualCam,
hotkeys::Hotkeys, inputs::Inputs, media_inputs::MediaInputs, outputs::Outputs,
profiles::Profiles, recording::Recording, replay_buffer::ReplayBuffer,
scene_collections::SceneCollections, scene_items::SceneItems, scenes::Scenes, sources::Sources,
streaming::Streaming, transitions::Transitions, ui::Ui, virtual_cam::VirtualCam,
};
#[cfg(feature = "events")]
use crate::events::Event;
@ -50,6 +50,7 @@ mod general;
mod hotkeys;
mod inputs;
mod media_inputs;
mod outputs;
mod profiles;
mod recording;
mod replay_buffer;
@ -247,6 +248,7 @@ impl Client {
trace!(
id = %response.id,
status = ?response.status,
data = %response.data,
"got request-response message",
);
receivers2.notify(response).await?;
@ -482,6 +484,11 @@ impl Client {
MediaInputs { client: self }
}
/// Access API functions related to outputs.
pub fn outputs(&self) -> Outputs<'_> {
Outputs { client: self }
}
/// Access API functions related to profiles.
pub fn profiles(&self) -> Profiles<'_> {
Profiles { client: self }

@ -0,0 +1,66 @@
use serde::{de::DeserializeOwned, Serialize};
use super::Client;
use crate::{requests::outputs::Request, responses::outputs as responses, Error, Result};
/// API functions related to outputs.
pub struct Outputs<'a> {
pub(super) client: &'a Client,
}
impl<'a> Outputs<'a> {
/// Gets the list of available outputs.
pub async fn list(&self) -> Result<Vec<responses::Output>> {
self.client
.send_message::<_, responses::OutputList>(Request::List)
.await
.map(|ol| ol.outputs)
}
/// Gets the status of an output.
pub async fn status(&self, name: &str) -> Result<responses::OutputStatus> {
self.client.send_message(Request::Status { name }).await
}
/// Toggles the status of an output.
pub async fn toggle(&self, name: &str) -> Result<bool> {
self.client
.send_message::<_, responses::OutputActive>(Request::Toggle { name })
.await
.map(|oa| oa.active)
}
/// Starts an output.
pub async fn start(&self, name: &str) -> Result<()> {
self.client.send_message(Request::Start { name }).await
}
/// Stops an output.
pub async fn stop(&self, name: &str) -> Result<()> {
self.client.send_message(Request::Stop { name }).await
}
/// Gets the settings of an output.
pub async fn settings<T>(&self, name: &str) -> Result<T>
where
T: DeserializeOwned,
{
self.client
.send_message::<_, responses::OutputSettings<T>>(Request::Settings { name })
.await
.map(|os| os.settings)
}
/// Sets the settings of an output.
pub async fn set_settings<T>(&self, name: &str, settings: T) -> Result<()>
where
T: Serialize,
{
self.client
.send_message(Request::SetSettings {
name,
settings: serde_json::to_value(&settings).map_err(Error::SerializeCustomData)?,
})
.await
}
}

@ -26,8 +26,11 @@ impl<'a> Recording<'a> {
}
/// Stops the record output.
pub async fn stop(&self) -> Result<()> {
self.client.send_message(Request::Stop).await
pub async fn stop(&self) -> Result<String> {
self.client
.send_message::<_, responses::OutputStopped>(Request::Stop)
.await
.map(|os| os.path)
}
/// Toggles pause on the record output.

@ -1,5 +1,9 @@
use super::Client;
use crate::{requests::ui::Request, responses::ui as responses, Result};
use crate::{
requests::ui::{OpenSourceProjector, OpenVideoMixProjector, Request},
responses::ui as responses,
Result,
};
/// API functions related to the user interface.
pub struct Ui<'a> {
@ -52,4 +56,18 @@ impl<'a> Ui<'a> {
.await
.map(|ml| ml.monitors)
}
/// Open a projector for a specific output video mix.
pub async fn open_video_mix_projector(&self, open: OpenVideoMixProjector<'a>) -> Result<()> {
self.client
.send_message(Request::OpenVideoMixProjector(open))
.await
}
/// Opens a projector for a source.
pub async fn open_source_projector(&self, open: OpenSourceProjector<'a>) -> Result<()> {
self.client
.send_message(Request::OpenSourceProjector(open))
.await
}
}

@ -311,6 +311,9 @@ pub enum Event {
/// The specific state of the output.
#[serde(rename = "outputState")]
state: OutputState,
/// File name for the saved recording, if record stopped.
#[serde(rename = "outputPath")]
path: Option<String>,
},
/// The state of the replay buffer output has changed.
ReplayBufferStateChanged {

@ -1,4 +1,4 @@
//! Requests related to media filters.
//! Requests related to media inputs.
use serde::Serialize;
use time::Duration;

@ -12,6 +12,7 @@ pub mod general;
pub mod hotkeys;
pub mod inputs;
pub(crate) mod media_inputs;
pub(crate) mod outputs;
pub mod profiles;
pub(crate) mod recording;
pub(crate) mod replay_buffer;
@ -21,7 +22,7 @@ pub mod scenes;
pub mod sources;
pub(crate) mod streaming;
pub(crate) mod transitions;
pub(crate) mod ui;
pub mod ui;
pub(crate) mod virtual_cam;
pub(crate) enum ClientRequest<'a> {
@ -215,6 +216,7 @@ pub(crate) enum RequestType<'a> {
Hotkeys(self::hotkeys::Request<'a>),
Inputs(self::inputs::Request<'a>),
MediaInputs(self::media_inputs::Request<'a>),
Outputs(self::outputs::Request<'a>),
Profiles(self::profiles::Request<'a>),
Recording(self::recording::Request),
ReplayBuffer(self::replay_buffer::Request),
@ -240,6 +242,7 @@ impl<'a> Serialize for RequestType<'a> {
Self::Hotkeys(req) => req.serialize(serializer),
Self::Inputs(req) => req.serialize(serializer),
Self::MediaInputs(req) => req.serialize(serializer),
Self::Outputs(req) => req.serialize(serializer),
Self::Profiles(req) => req.serialize(serializer),
Self::Recording(req) => req.serialize(serializer),
Self::ReplayBuffer(req) => req.serialize(serializer),

@ -0,0 +1,55 @@
//! Requests related to outputs.
use serde::Serialize;
#[derive(Serialize)]
#[serde(tag = "requestType", content = "requestData")]
pub(crate) enum Request<'a> {
#[serde(rename = "GetOutputList")]
List,
#[serde(rename = "GetOutputStatus")]
Status {
/// Output name.
#[serde(rename = "outputName")]
name: &'a str,
},
#[serde(rename = "ToggleOutput")]
Toggle {
/// Output name.
#[serde(rename = "outputName")]
name: &'a str,
},
#[serde(rename = "StartOutput")]
Start {
/// Output name.
#[serde(rename = "outputName")]
name: &'a str,
},
#[serde(rename = "StopOutput")]
Stop {
/// Output name.
#[serde(rename = "outputName")]
name: &'a str,
},
#[serde(rename = "GetOutputSettings")]
Settings {
/// Output name.
#[serde(rename = "outputName")]
name: &'a str,
},
#[serde(rename = "SetOutputSettings")]
SetSettings {
/// Output name.
#[serde(rename = "outputName")]
name: &'a str,
/// Output settings.
#[serde(rename = "outputSettings")]
settings: serde_json::Value,
},
}
impl<'a> From<Request<'a>> for super::RequestType<'a> {
fn from(value: Request<'a>) -> Self {
super::RequestType::Outputs(value)
}
}

@ -1,4 +1,4 @@
//! Requests related to outputs.
//! Requests related to the replay buffer.
use serde::Serialize;

@ -79,7 +79,7 @@ pub(crate) enum Request<'a> {
},
#[serde(rename = "SetSceneItemIndex")]
SetIndex(SetIndex<'a>),
#[serde(rename = "SetSceneItemIndex")]
#[serde(rename = "GetSceneItemPrivateSettings")]
PrivateSettings {
/// Name of the scene the item is in.
#[serde(rename = "sceneName")]

@ -33,6 +33,10 @@ pub(crate) enum Request<'a> {
},
#[serde(rename = "GetMonitorList")]
GetMonitorList,
#[serde(rename = "OpenVideoMixProjector")]
OpenVideoMixProjector(OpenVideoMixProjector<'a>),
#[serde(rename = "OpenSourceProjector")]
OpenSourceProjector(OpenSourceProjector<'a>),
}
impl<'a> From<Request<'a>> for super::RequestType<'a> {
@ -40,3 +44,52 @@ impl<'a> From<Request<'a>> for super::RequestType<'a> {
super::RequestType::Ui(value)
}
}
/// Request information for [`crate::client::Ui::open_video_mix_projector`].
#[derive(Serialize)]
pub struct OpenVideoMixProjector<'a> {
/// Type of mix to open.
#[serde(rename = "videoMixType")]
pub r#type: VideoMixType,
/// Optional location for the new projector window.
pub location: Option<Location<'a>>,
}
/// Request information for [`crate::client::Ui::open_source_projector`].
#[derive(Serialize)]
pub struct OpenSourceProjector<'a> {
/// Name of the source to open a projector for.
#[serde(rename = "sourceName")]
pub source: &'a str,
/// Optional location for the new projector window.
#[serde(flatten)]
pub location: Option<Location<'a>>,
}
/// Request information for [`crate::client::Ui::open_video_mix_projector`] as part of
/// [`OpenVideoMixProjector`] and [`crate::client::Ui::open_source_projector`] as part of
/// [`OpenSourceProjector`], describing the open location of the projector.
#[derive(Serialize)]
pub enum Location<'a> {
/// Monitor index, passing `-1` opens the projector in windowed mode.
#[serde(rename = "monitorIndex")]
MonitorIndex(i32),
/// Size/Position data for a windowed projector, in `Qt Base64` encoded format.
#[serde(rename = "projectorGeometry")]
ProjectorGeometry(&'a str),
}
/// Request information for [`crate::client::Ui::open_video_mix_projector`] as part of
/// [`OpenVideoMixProjector`], defining the type of video mix to open.
#[derive(Serialize)]
pub enum VideoMixType {
/// Show the preview scene.
#[serde(rename = "OBS_WEBSOCKET_VIDEO_MIX_TYPE_PREVIEW")]
Preview,
/// Show the program scene.
#[serde(rename = "OBS_WEBSOCKET_VIDEO_MIX_TYPE_PROGRAM")]
Program,
/// Show a multi-view.
#[serde(rename = "OBS_WEBSOCKET_VIDEO_MIX_TYPE_MULTIVIEW")]
Multiview,
}

@ -54,7 +54,11 @@ pub struct Stats {
/// Response value for [`crate::client::General::call_vendor_request`].
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct CallVendorResponse<T> {
pub struct VendorResponse<T> {
/// Name of the vendor.
pub vendor_name: String,
/// Type of request.
pub request_type: String,
/// Object containing appropriate response data.
pub response_data: T,
}

@ -6,6 +6,7 @@ pub mod general;
pub(crate) mod hotkeys;
pub mod inputs;
pub mod media_inputs;
pub mod outputs;
pub mod profiles;
pub mod recording;
pub(crate) mod replay_buffer;

@ -0,0 +1,94 @@
//! Responses related to outputs.
use serde::Deserialize;
use time::Duration;
#[derive(Debug, Deserialize)]
pub(crate) struct OutputList {
pub outputs: Vec<Output>,
}
/// Response value for [`crate::client::Outputs::list`].
#[derive(Debug, Deserialize)]
pub struct Output {
/// Name of this output.
#[serde(rename = "outputName")]
pub name: String,
/// The kind of output.
#[serde(rename = "outputKind")]
pub kind: String,
/// Horizontal dimension of the output (if it is a video output).
#[serde(rename = "outputWidth")]
pub width: u32,
/// Vertical dimension of the output (if it is a video output).
#[serde(rename = "outputHeight")]
pub height: u32,
/// Whether this output is currently active.
#[serde(rename = "outputActive")]
pub active: bool,
/// Additional flags to describe capabilities of the output.
#[serde(rename = "outputFlags")]
pub flags: OutputFlags,
}
/// Response value for [`crate::client::Outputs::list`] as part of [`Output`].
#[derive(Debug, Deserialize)]
pub struct OutputFlags {
/// Output supports audio.
#[serde(rename = "OBS_OUTPUT_AUDIO")]
pub audio: bool,
/// Output supports video.
#[serde(rename = "OBS_OUTPUT_VIDEO")]
pub video: bool,
/// Output encodes data.
#[serde(rename = "OBS_OUTPUT_ENCODED")]
pub encoded: bool,
/// Output supports multiple audio/video tracks.
#[serde(rename = "OBS_OUTPUT_MULTI_TRACK")]
pub multi_track: bool,
/// Output is a service.
#[serde(rename = "OBS_OUTPUT_SERVICE")]
pub service: bool,
}
/// Response value for [`crate::client::Outputs::status`].
#[derive(Debug, Deserialize)]
pub struct OutputStatus {
/// Whether the output is active.
#[serde(rename = "outputActive")]
pub active: bool,
/// Whether the output is currently reconnecting.
#[serde(rename = "outputReconnecting")]
pub reconnecting: bool,
/// Current time code for the output.
#[serde(rename = "outputTimecode", with = "crate::serde::duration_timecode")]
pub timecode: Duration,
/// Current duration for the output.
#[serde(rename = "outputDuration", with = "crate::serde::duration_millis")]
pub duration: Duration,
/// Congestion of the output.
#[serde(rename = "outputCongestion")]
pub congestion: f32,
/// Number of bytes sent by the output.
#[serde(rename = "outputBytes")]
pub bytes: u64,
/// Number of frames skipped by the output's process.
#[serde(rename = "outputSkippedFrames")]
pub skipped_frames: u32,
/// Total number of frames delivered by the output's process.
#[serde(rename = "outputTotalFrames")]
pub total_frames: u32,
}
#[derive(Debug, Deserialize)]
pub(crate) struct OutputActive {
/// New state of the stream output.
#[serde(rename = "outputActive")]
pub active: bool,
}
#[derive(Debug, Deserialize)]
pub(crate) struct OutputSettings<T> {
#[serde(rename = "outputSettings")]
pub settings: T,
}

@ -24,7 +24,6 @@ pub struct RecordStatus {
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct OutputActive {
/// New state of the stream output.
#[serde(rename = "outputActive")]
@ -32,7 +31,13 @@ pub(crate) struct OutputActive {
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct OutputStopped {
/// File name for the saved recording.
#[serde(rename = "outputPath")]
pub path: String,
}
#[derive(Debug, Deserialize)]
pub(crate) struct OutputPaused {
#[serde(rename = "outputPaused")]
pub paused: bool,

@ -3,7 +3,6 @@
use serde::Deserialize;
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct OutputActive {
/// New state of the stream output.
#[serde(rename = "outputActive")]
@ -11,7 +10,7 @@ pub(crate) struct OutputActive {
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct SavedReplayPath {
#[serde(rename = "savedReplayPath")]
pub saved_replay_path: String,
}

@ -5,7 +5,6 @@ use time::Duration;
/// Response value for [`crate::client::Streaming::status`].
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StreamStatus {
/// Whether the output is active.
#[serde(rename = "outputActive")]
@ -19,6 +18,9 @@ pub struct StreamStatus {
/// Current duration for the output.
#[serde(rename = "outputDuration", with = "crate::serde::duration_millis")]
pub duration: Duration,
/// Congestion of the output.
#[serde(rename = "outputCongestion")]
pub congestion: f32,
/// Number of bytes sent by the output.
#[serde(rename = "outputBytes")]
pub bytes: u64,
@ -31,7 +33,6 @@ pub struct StreamStatus {
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct OutputActive {
/// New state of the stream output.
#[serde(rename = "outputActive")]

@ -24,6 +24,9 @@ pub struct Monitor {
/// Name of this monitor.
#[serde(rename = "monitorName")]
pub name: String,
/// Positional index in the list of monitors.
#[serde(rename = "monitorIndex")]
pub index: u32,
/// Pixel size.
#[serde(flatten)]
pub size: MonitorSize,

@ -7,6 +7,7 @@ mod general;
mod hotkeys;
mod inputs;
mod media_inputs;
mod outputs;
mod profiles;
mod recording;
mod replay_buffer;

@ -0,0 +1,67 @@
#![cfg(feature = "test-integration")]
use std::time::Duration;
use anyhow::Result;
use obws::events::{Event, OutputState};
use tokio::time;
use crate::{common, wait_for};
const OUTPUT_VIRTUALCAM: &str = "virtualcam_output";
#[tokio::test]
async fn outputs() -> Result<()> {
let client = common::new_client().await?;
let events = client.events()?;
let client = client.outputs();
tokio::pin!(events);
client.list().await?;
client.status(OUTPUT_VIRTUALCAM).await?;
client.toggle(OUTPUT_VIRTUALCAM).await?;
wait_for!(
events,
Event::VirtualcamStateChanged {
state: OutputState::Started,
..
}
);
time::sleep(Duration::from_secs(1)).await;
client.toggle(OUTPUT_VIRTUALCAM).await?;
wait_for!(
events,
Event::VirtualcamStateChanged {
state: OutputState::Stopped,
..
}
);
time::sleep(Duration::from_secs(1)).await;
client.start(OUTPUT_VIRTUALCAM).await?;
wait_for!(
events,
Event::VirtualcamStateChanged {
state: OutputState::Started,
..
}
);
time::sleep(Duration::from_secs(1)).await;
client.stop(OUTPUT_VIRTUALCAM).await?;
wait_for!(
events,
Event::VirtualcamStateChanged {
state: OutputState::Stopped,
..
}
);
time::sleep(Duration::from_secs(1)).await;
let settings = client
.settings::<serde_json::Value>(OUTPUT_VIRTUALCAM)
.await?;
client.set_settings(OUTPUT_VIRTUALCAM, &settings).await?;
Ok(())
}

@ -119,5 +119,20 @@ async fn scene_items() -> Result<()> {
})
.await?;
let _settings = client
.private_settings::<serde_json::Value>(TEST_SCENE, test_text_id)
.await?;
// TODO: Currently obs-websocket doesn't accept empty objects `{}`, and this fails as our
// test scene item doesn't have any private settings.
//
// client
// .set_private_settings(SetPrivateSettings {
// scene: TEST_SCENE,
// item_id: test_text_id,
// settings: &settings,
// })
// .await?;
Ok(())
}

@ -1,6 +1,7 @@
use anyhow::Result;
use obws::requests::ui::{Location, OpenSourceProjector, OpenVideoMixProjector, VideoMixType};
use crate::common;
use crate::common::{self, TEST_TEXT};
#[tokio::test]
async fn ui() -> Result<()> {
@ -11,5 +12,19 @@ async fn ui() -> Result<()> {
client.set_studio_mode_enabled(!enabled).await?;
client.set_studio_mode_enabled(enabled).await?;
client.list_monitors().await?;
client
.open_video_mix_projector(OpenVideoMixProjector {
r#type: VideoMixType::Preview,
location: None,
})
.await?;
client
.open_source_projector(OpenSourceProjector {
source: TEST_TEXT,
location: Some(Location::MonitorIndex(-1)),
})
.await?;
Ok(())
}

Loading…
Cancel
Save