Adjust to the latest changes from obs-websocket

v5-api
Dominik Nakamura 3 years ago
parent 430b408ea9
commit 1f2a7c155b
No known key found for this signature in database
GPG Key ID: E4C6A749B2491910

@ -53,3 +53,7 @@ required-features = ["events"]
[[test]]
name = "general"
required-features = ["events"]
[[test]]
name = "recording"
required-features = ["events"]

@ -1,6 +1,6 @@
set dotenv-load := true
nightly := "nightly-2021-10-23"
nightly := "nightly-2021-11-18"
# list available recipes
default:

@ -0,0 +1,56 @@
use time::Duration;
use super::Client;
use crate::{common::MediaAction, requests::RequestType, responses, Result};
/// API functions related to media inputs.
pub struct MediaInputs<'a> {
pub(super) client: &'a Client,
}
impl<'a> MediaInputs<'a> {
pub async fn get_media_input_status(&self, input_name: &str) -> Result<responses::MediaStatus> {
self.client
.send_message(RequestType::GetMediaInputStatus { input_name })
.await
}
pub async fn set_media_input_cursor(
&self,
input_name: &str,
media_cursor: Duration,
) -> Result<()> {
self.client
.send_message(RequestType::SetMediaInputCursor {
input_name,
media_cursor,
})
.await
}
pub async fn offset_media_input_cursor(
&self,
input_name: &str,
media_cursor_offset: Duration,
) -> Result<()> {
self.client
.send_message(RequestType::OffsetMediaInputCursor {
input_name,
media_cursor_offset,
})
.await
}
pub async fn trigger_media_input_action(
&self,
input_name: &str,
media_action: MediaAction,
) -> Result<()> {
self.client
.send_message(RequestType::TriggerMediaInputAction {
input_name,
media_action,
})
.await
}
}

@ -29,8 +29,9 @@ use tokio::{
use tokio_tungstenite::{tungstenite::Message, MaybeTlsStream, WebSocketStream};
pub use self::{
config::Config, general::General, inputs::Inputs, scene_items::SceneItems, scenes::Scenes,
sources::Sources, streaming::Streaming,
config::Config, general::General, inputs::Inputs, media_inputs::MediaInputs,
recording::Recording, scene_items::SceneItems, scenes::Scenes, sources::Sources,
streaming::Streaming,
};
#[cfg(feature = "events")]
use crate::events::Event;
@ -43,6 +44,8 @@ use crate::{
mod config;
mod general;
mod inputs;
mod media_inputs;
mod recording;
mod scene_items;
mod scenes;
mod sources;
@ -406,6 +409,16 @@ impl Client {
Inputs { client: self }
}
/// Access API functions related to media inputs.
pub fn media_inputs(&self) -> MediaInputs<'_> {
MediaInputs { client: self }
}
/// Access API functions related to recording.
pub fn recording(&self) -> Recording<'_> {
Recording { client: self }
}
/// Access API functions related to scene items.
pub fn scene_items(&self) -> SceneItems<'_> {
SceneItems { client: self }

@ -0,0 +1,52 @@
use super::Client;
use crate::{requests::RequestType, responses, Result};
/// API functions related to recording.
pub struct Recording<'a> {
pub(super) client: &'a Client,
}
impl<'a> Recording<'a> {
pub async fn get_record_status(&self) -> Result<responses::RecordStatus> {
self.client.send_message(RequestType::GetRecordStatus).await
}
pub async fn toggle_record(&self) -> Result<bool> {
self.client
.send_message::<responses::OutputActive>(RequestType::ToggleRecord)
.await
.map(|oa| oa.output_active)
}
pub async fn start_record(&self) -> Result<()> {
self.client.send_message(RequestType::StartRecord).await
}
pub async fn stop_record(&self) -> Result<()> {
self.client.send_message(RequestType::StopRecord).await
}
pub async fn toggle_record_pause(&self) -> Result<bool> {
self.client
.send_message::<responses::OutputPaused>(RequestType::ToggleRecordPause)
.await
.map(|op| op.output_paused)
}
pub async fn pause_record(&self) -> Result<()> {
self.client.send_message(RequestType::PauseRecord).await
}
pub async fn resume_record(&self) -> Result<()> {
self.client.send_message(RequestType::ResumeRecord).await
}
// Currently disabled in obs-websocket and will always fail.
#[doc(hidden)]
pub async fn get_record_directory(&self) -> Result<String> {
self.client
.send_message::<responses::RecordDirectory>(RequestType::GetRecordDirectory)
.await
.map(|rd| rd.record_directory)
}
}

@ -1,7 +1,8 @@
use super::Client;
use crate::{
requests::{
CreateSceneItem, RequestType, SetSceneItemEnabled, SetSceneItemIndex, SetSceneItemLocked,
CreateSceneItem, DuplicateSceneItem, RequestType, SetSceneItemEnabled, SetSceneItemIndex,
SetSceneItemLocked, SetSceneItemTransform,
},
responses, Result,
};
@ -48,7 +49,7 @@ impl<'a> SceneItems<'a> {
.map(|sii| sii.scene_item_id)
}
pub async fn remote_scene_item(&self, scene_name: &str, scene_item_id: i64) -> Result<()> {
pub async fn remove_scene_item(&self, scene_name: &str, scene_item_id: i64) -> Result<()> {
self.client
.send_message(RequestType::RemoveSceneItem {
scene_name,
@ -57,6 +58,13 @@ impl<'a> SceneItems<'a> {
.await
}
pub async fn duplicate_scene_item(&self, duplicate: DuplicateSceneItem<'_>) -> Result<i64> {
self.client
.send_message::<responses::SceneItemId>(RequestType::DuplicateSceneItem(duplicate))
.await
.map(|sii| sii.scene_item_id)
}
pub async fn get_scene_item_transform(
&self,
scene_name: &str,
@ -70,6 +78,15 @@ impl<'a> SceneItems<'a> {
.await
}
pub async fn set_scene_item_transform(
&self,
transform: SetSceneItemTransform<'_>,
) -> Result<()> {
self.client
.send_message(RequestType::SetSceneItemTransform(transform))
.await
}
pub async fn get_scene_item_enabled(
&self,
scene_name: &str,

@ -13,7 +13,7 @@ impl<'a> Streaming<'a> {
pub async fn toggle_stream(&self) -> Result<bool> {
self.client
.send_message::<responses::ToggleStream>(RequestType::ToggleStream)
.send_message::<responses::OutputActive>(RequestType::ToggleStream)
.await
.map(|ts| ts.output_active)
}

@ -0,0 +1,48 @@
use bitflags::bitflags;
use serde::{Deserialize, Serialize};
bitflags! {
#[derive(Serialize, Deserialize)]
#[serde(transparent)]
pub struct Alignment: u32 {
const CENTER = 0;
const LEFT = 1 << 0;
const RIGHT = 1 << 1;
const TOP = 1 << 2;
const BOTTOM = 1 << 3;
}
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub enum BoundsType {
#[serde(rename = "OBS_BOUNDS_NONE")]
None,
#[serde(rename = "OBS_BOUNDS_STRETCH")]
Stretch,
#[serde(rename = "OBS_BOUNDS_SCALE_INNER")]
ScaleInner,
#[serde(rename = "OBS_BOUNDS_SCALE_OUTER")]
ScaleOuter,
#[serde(rename = "OBS_BOUNDS_SCALE_TO_WIDTH")]
ScaleToWidth,
#[serde(rename = "OBS_BOUNDS_SCALE_TO_HEIGHT")]
ScaleToHeight,
#[serde(rename = "OBS_BOUNDS_MAX_ONLY")]
MaxOnly,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub enum MediaAction {
#[serde(rename = "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY")]
Play,
#[serde(rename = "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE")]
Pause,
#[serde(rename = "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP")]
Stop,
#[serde(rename = "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART")]
Restart,
#[serde(rename = "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NEXT")]
Next,
#[serde(rename = "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS")]
Previous,
}

@ -96,6 +96,39 @@ impl<'de> Visitor<'de> for DurationMillisVisitor {
}
}
pub fn duration_millis_opt<'de, D>(deserializer: D) -> Result<Option<Duration>, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_option(DurationMillisOptVisitor)
}
struct DurationMillisOptVisitor;
impl<'de> Visitor<'de> for DurationMillisOptVisitor {
type Value = Option<Duration>;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("a duration in milliseconds")
}
fn visit_none<E>(self) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(None)
}
fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
deserializer
.deserialize_i64(DurationMillisVisitor)
.map(Some)
}
}
pub fn duration_nanos<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where
D: Deserializer<'de>,
@ -217,6 +250,55 @@ mod tests {
);
}
#[test]
fn deser_duration_millis_opt() {
#[derive(Debug, PartialEq, Eq, Deserialize)]
struct SimpleDuration {
#[serde(deserialize_with = "duration_millis_opt")]
value: Option<Duration>,
}
assert_de_tokens(
&SimpleDuration { value: None },
&[
Token::Struct {
name: "SimpleDuration",
len: 1,
},
Token::Str("value"),
Token::None,
Token::StructEnd,
],
);
assert_de_tokens(
&SimpleDuration {
value: Some(Duration::milliseconds(150)),
},
&[
Token::Struct {
name: "SimpleDuration",
len: 1,
},
Token::Str("value"),
Token::Some,
Token::I64(150),
Token::StructEnd,
],
);
assert_de_tokens_error::<SimpleDuration>(
&[
Token::Struct {
name: "SimpleDuration",
len: 0,
},
Token::StructEnd,
],
"missing field `value`",
);
}
#[test]
fn deser_duration_nanos() {
#[derive(Debug, PartialEq, Eq, Deserialize)]

@ -5,7 +5,7 @@ use std::{collections::BTreeMap, path::PathBuf};
use serde::Deserialize;
use time::Duration;
use crate::{responses::SceneItemTransform, MonitorType};
use crate::{common::MediaAction, responses::SceneItemTransform, MonitorType};
/// All possible event types that can occur while the user interacts with OBS.
#[derive(Clone, Debug, Deserialize)]
@ -168,7 +168,7 @@ pub enum Event {
scene_item_index: u32,
},
#[serde(rename_all = "camelCase")]
SceneItemReindexed {
SceneItemListReindexed {
scene_name: String,
scene_items: Vec<BasicSceneItem>,
},
@ -250,24 +250,6 @@ pub enum Event {
Unknown,
}
#[derive(Clone, Copy, Debug, Deserialize)]
pub enum MediaAction {
#[serde(rename = "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE")]
Pause,
#[serde(rename = "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY")]
Play,
#[serde(rename = "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART")]
Restart,
#[serde(rename = "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP")]
Stop,
#[serde(rename = "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NEXT")]
Next,
#[serde(rename = "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS")]
Previous,
#[serde(other)]
Unknown,
}
#[derive(Clone, Copy, Debug, Deserialize)]
pub enum OutputState {
#[serde(rename = "OBS_WEBSOCKET_OUTPUT_STARTING")]

@ -36,6 +36,7 @@ pub use semver::{Comparator, Version};
pub use self::client::Client;
pub mod client;
pub mod common;
#[cfg(feature = "events")]
pub mod events;
pub mod requests;

@ -7,7 +7,10 @@ use serde::{ser::SerializeStruct, Serialize};
use serde_with::skip_serializing_none;
use time::Duration;
use crate::MonitorType;
use crate::{
common::{Alignment, BoundsType, MediaAction},
MonitorType,
};
mod ser;
@ -262,7 +265,7 @@ pub(crate) enum RequestType<'a> {
#[serde(rename_all = "camelCase")]
SetInputAudioSyncOffset {
input_name: &'a str,
#[serde(serialize_with = "ser::duration_nanos")]
#[serde(serialize_with = "ser::duration_millis")]
input_audio_sync_offset: Duration,
},
#[serde(rename_all = "camelCase")]
@ -285,6 +288,41 @@ pub(crate) enum RequestType<'a> {
property_name: &'a str,
},
// --------------------------------
// Media inputs
// --------------------------------
#[serde(rename_all = "camelCase")]
GetMediaInputStatus {
input_name: &'a str,
},
#[serde(rename_all = "camelCase")]
SetMediaInputCursor {
input_name: &'a str,
#[serde(serialize_with = "ser::duration_millis")]
media_cursor: Duration,
},
#[serde(rename_all = "camelCase")]
OffsetMediaInputCursor {
input_name: &'a str,
#[serde(serialize_with = "ser::duration_millis")]
media_cursor_offset: Duration,
},
#[serde(rename_all = "camelCase")]
TriggerMediaInputAction {
input_name: &'a str,
media_action: MediaAction,
},
// --------------------------------
// Recording
// --------------------------------
GetRecordStatus,
ToggleRecord,
StartRecord,
StopRecord,
ToggleRecordPause,
PauseRecord,
ResumeRecord,
GetRecordDirectory,
// --------------------------------
// Scene items
// --------------------------------
#[serde(rename_all = "camelCase")]
@ -306,11 +344,13 @@ pub(crate) enum RequestType<'a> {
scene_name: &'a str,
scene_item_id: i64,
},
DuplicateSceneItem(DuplicateSceneItem<'a>),
#[serde(rename_all = "camelCase")]
GetSceneItemTransform {
scene_name: &'a str,
scene_item_id: i64,
},
SetSceneItemTransform(SetSceneItemTransform<'a>),
#[serde(rename_all = "camelCase")]
GetSceneItemEnabled {
scene_name: &'a str,
@ -479,6 +519,41 @@ pub struct CreateSceneItem<'a> {
pub scene_item_enabled: Option<bool>,
}
#[derive(Default, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DuplicateSceneItem<'a> {
pub scene_name: &'a str,
pub scene_item_id: i64,
pub destination_scene_name: Option<&'a str>,
}
#[derive(Default, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SetSceneItemTransform<'a> {
pub scene_name: &'a str,
pub scene_item_id: i64,
pub scene_item_transform: SceneItemTransform,
}
#[derive(Default, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SceneItemTransform {
pub position_x: Option<i32>,
pub position_y: Option<i32>,
pub rotation: Option<f32>,
pub scale_x: Option<f32>,
pub scale_y: Option<f32>,
pub alignment: Option<Alignment>,
pub bounds_type: Option<BoundsType>,
pub bounds_alignment: Option<Alignment>,
pub bounds_width: Option<u32>,
pub bounds_height: Option<u32>,
pub crop_left: Option<u32>,
pub crop_right: Option<u32>,
pub crop_top: Option<u32>,
pub crop_bottom: Option<u32>,
}
#[derive(Default, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SetSceneItemEnabled<'a> {

@ -1,9 +1,9 @@
use serde::Serializer;
use time::Duration;
pub fn duration_nanos<S>(value: &Duration, serializer: S) -> Result<S::Ok, S::Error>
pub fn duration_millis<S>(value: &Duration, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_i128(value.whole_nanoseconds())
serializer.serialize_i128(value.whole_milliseconds())
}

@ -1,12 +1,14 @@
//! All responses that can be received from the API.
use bitflags::bitflags;
pub use semver::Version as SemVerVersion;
use serde::{de, Deserialize, Deserializer};
use serde_repr::Deserialize_repr;
use time::Duration;
use crate::MonitorType;
use crate::{
common::{Alignment, BoundsType},
MonitorType,
};
#[derive(Debug)]
pub(crate) enum ServerMessage {
@ -169,6 +171,8 @@ pub enum StatusCode {
StudioModeActive = 504,
/// Studio mode is not active and should be.
StudioModeNotActive = 505,
/// An output is not paused and should be
OutputNotPaused = 506,
/// The resource was not found.
ResourceNotFound = 600,
/// The resource already exists.
@ -306,6 +310,66 @@ pub struct InputVolume {
pub input_volume_db: f32,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MediaStatus {
pub media_state: MediaState,
#[serde(deserialize_with = "crate::de::duration_millis_opt")]
pub media_duration: Option<Duration>,
#[serde(deserialize_with = "crate::de::duration_millis_opt")]
pub media_cursor: Option<Duration>,
}
#[derive(Copy, Clone, Debug, Deserialize)]
pub enum MediaState {
#[serde(rename = "OBS_MEDIA_STATE_NONE")]
None,
#[serde(rename = "OBS_MEDIA_STATE_PLAYING")]
Playing,
#[serde(rename = "OBS_MEDIA_STATE_OPENING")]
Opening,
#[serde(rename = "OBS_MEDIA_STATE_BUFFERING")]
Buffering,
#[serde(rename = "OBS_MEDIA_STATE_PAUSED")]
Paused,
#[serde(rename = "OBS_MEDIA_STATE_STOPPED")]
Stopped,
#[serde(rename = "OBS_MEDIA_STATE_ENDED")]
Ended,
#[serde(rename = "OBS_MEDIA_STATE_ERROR")]
Error,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RecordStatus {
pub output_active: bool,
pub output_paused: bool,
#[serde(deserialize_with = "crate::de::duration_timecode")]
pub output_timecode: Duration,
#[serde(deserialize_with = "crate::de::duration_nanos")]
pub output_duration: Duration,
pub output_bytes: u64,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct OutputActive {
pub output_active: bool,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct OutputPaused {
pub output_paused: bool,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct RecordDirectory {
pub record_directory: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct SceneItemId {
@ -320,6 +384,8 @@ pub struct SceneItemTransform {
pub position_x: i32,
pub position_y: i32,
pub rotation: f32,
pub scale_x: f32,
pub scale_y: f32,
pub width: u32,
pub height: u32,
pub alignment: Alignment,
@ -333,36 +399,6 @@ pub struct SceneItemTransform {
pub crop_bottom: u32,
}
bitflags! {
#[derive(Deserialize)]
#[serde(transparent)]
pub struct Alignment: u32 {
const CENTER = 0;
const LEFT = 1 << 0;
const RIGHT = 1 << 1;
const TOP = 1 << 2;
const BOTTOM = 1 << 3;
}
}
#[derive(Clone, Copy, Debug, Deserialize)]
pub enum BoundsType {
#[serde(rename = "OBS_BOUNDS_NONE")]
None,
#[serde(rename = "OBS_BOUNDS_STRETCH")]
Stretch,
#[serde(rename = "OBS_BOUNDS_SCALE_INNER")]
ScaleInner,
#[serde(rename = "OBS_BOUNDS_SCALE_OUTER")]
ScaleOuter,
#[serde(rename = "OBS_BOUNDS_SCALE_TO_WIDTH")]
ScaleToWidth,
#[serde(rename = "OBS_BOUNDS_SCALE_TO_HEIGHT")]
ScaleToHeight,
#[serde(rename = "OBS_BOUNDS_MAX_ONLY")]
MaxOnly,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct SceneItemEnabled {
@ -416,7 +452,26 @@ pub(crate) struct SceneItemList {
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SceneItem {}
pub struct SceneItem {
scene_item_id: i64,
scene_item_index: u32,
source_name: String,
source_type: SourceType,
input_kind: Option<String>,
is_group: Option<bool>,
}
#[derive(Copy, Clone, Debug, Deserialize)]
pub enum SourceType {
#[serde(rename = "OBS_SOURCE_TYPE_INPUT")]
Input,
#[serde(rename = "OBS_SOURCE_TYPE_FILTER")]
Filter,
#[serde(rename = "OBS_SOURCE_TYPE_TRANSITION")]
Transition,
#[serde(rename = "OBS_SOURCE_TYPE_SCENE")]
Scene,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
@ -471,9 +526,3 @@ pub struct StreamStatus {
pub output_skipped_frames: u32,
pub output_total_frames: u32,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ToggleStream {
pub output_active: bool,
}

@ -102,6 +102,12 @@ async fn ensure_obs_setup(client: &Client) -> Result<()> {
"studio mode enabled, required to be disabled for studio mode tests"
);
let recording_active = client.recording().get_record_status().await?.output_active;
ensure!(
!recording_active,
"recording active, required to be stopped for recording tests"
);
client
.scenes()
.set_current_program_scene(TEST_SCENE)
@ -164,7 +170,7 @@ fn is_required_profile(profile: &str) -> bool {
#[macro_export]
macro_rules! wait_for {
($expression:expr, $pattern:pat) => {
($expression:expr, $pattern:pat) => {{
use futures_util::stream::StreamExt;
while let Some(event) = $expression.next().await {
@ -172,5 +178,5 @@ macro_rules! wait_for {
break;
}
}
};
}};
}

@ -57,8 +57,7 @@ async fn main() -> Result<()> {
client.create_profile("OBWS-TEST-New-Profile").await?;
client.remove_profile("OBWS-TEST-New-Profile").await?;
// Currently broken in obs-websocket
// client.get_profile_parameter("General", "Name").await?;
client.get_profile_parameter("General", "Name").await?;
client
.set_profile_parameter(SetProfileParameter {
parameter_category: "OBWS",

@ -1,7 +1,11 @@
#![cfg(feature = "test-integration")]
use anyhow::Result;
use obws::requests::{SetInputSettings, Volume};
use obws::{
requests::{SetInputSettings, Volume},
MonitorType,
};
use time::Duration;
use crate::common::{INPUT_KIND_BROWSER, TEST_BROWSER, TEST_BROWSER_RENAME, TEST_MEDIA};
@ -48,5 +52,21 @@ async fn main() -> Result<()> {
.set_input_name(TEST_BROWSER_RENAME, TEST_BROWSER)
.await?;
let offset = client.get_input_audio_sync_offset(TEST_MEDIA).await?;
client
.set_input_audio_sync_offset(TEST_MEDIA, Duration::milliseconds(500))
.await?;
client
.set_input_audio_sync_offset(TEST_MEDIA, offset)
.await?;
let monitor_type = client.get_input_audio_monitor_type(TEST_MEDIA).await?;
client
.set_input_audio_monitor_type(TEST_MEDIA, MonitorType::MonitorAndOutput)
.await?;
client
.set_input_audio_monitor_type(TEST_MEDIA, monitor_type)
.await?;
Ok(())
}

@ -0,0 +1,28 @@
#![cfg(feature = "test-integration")]
use anyhow::Result;
use obws::common::MediaAction;
use time::Duration;
use crate::common::TEST_MEDIA;
mod common;
#[tokio::test]
async fn main() -> Result<()> {
let client = common::new_client().await?;
let client = client.media_inputs();
client.get_media_input_status(TEST_MEDIA).await?;
client
.set_media_input_cursor(TEST_MEDIA, Duration::seconds(1))
.await?;
client
.offset_media_input_cursor(TEST_MEDIA, Duration::seconds(1))
.await?;
client
.trigger_media_input_action(TEST_MEDIA, MediaAction::Next)
.await?;
Ok(())
}

@ -0,0 +1,96 @@
#![cfg(feature = "test-integration")]
use std::time::Duration;
use anyhow::Result;
use obws::events::{Event, OutputState};
use tokio::time;
mod common;
#[tokio::test]
async fn main() -> Result<()> {
let client = common::new_client().await?;
let events = client.events()?;
let client = client.recording();
tokio::pin!(events);
client.get_record_status().await?;
client.start_record().await?;
wait_for!(
events,
Event::RecordStateChanged {
output_state: OutputState::Started,
..
}
);
time::sleep(Duration::from_secs(1)).await;
client.pause_record().await?;
wait_for!(
events,
Event::RecordStateChanged {
output_state: OutputState::Paused,
..
}
);
time::sleep(Duration::from_secs(1)).await;
client.resume_record().await?;
wait_for!(
events,
Event::RecordStateChanged {
output_state: OutputState::Resumed,
..
}
);
time::sleep(Duration::from_secs(1)).await;
client.stop_record().await?;
wait_for!(
events,
Event::RecordStateChanged {
output_state: OutputState::Stopped,
..
}
);
time::sleep(Duration::from_secs(1)).await;
client.toggle_record().await?;
wait_for!(
events,
Event::RecordStateChanged {
output_state: OutputState::Started,
..
}
);
time::sleep(Duration::from_secs(1)).await;
client.toggle_record_pause().await?;
wait_for!(
events,
Event::RecordStateChanged {
output_state: OutputState::Paused,
..
}
);
time::sleep(Duration::from_secs(1)).await;
client.toggle_record_pause().await?;
wait_for!(
events,
Event::RecordStateChanged {
output_state: OutputState::Resumed,
..
}
);
time::sleep(Duration::from_secs(1)).await;
client.toggle_record().await?;
wait_for!(
events,
Event::RecordStateChanged {
output_state: OutputState::Stopped,
..
}
);
time::sleep(Duration::from_secs(1)).await;
Ok(())
}
Loading…
Cancel
Save