Compare commits
4 Commits
cd614c0956
...
2ec4c231c2
Author | SHA1 | Date |
---|---|---|
|
2ec4c231c2 | |
|
a419a78a0a | |
|
5fd5b6f764 | |
|
be7817cb10 |
|
@ -1,14 +1,24 @@
|
|||
pub struct AtemPacket {
|
||||
length: u16,
|
||||
flags: u8,
|
||||
session_id: u16,
|
||||
remote_packet_id: u16,
|
||||
body: Vec<u8>,
|
||||
use core::{fmt::Display, str};
|
||||
|
||||
// TODO: we don't need itertools once https://github.com/rust-lang/rust/issues/79524 lands
|
||||
use itertools::Itertools;
|
||||
|
||||
/// The "hello" packet to start communication with the ATEM
|
||||
pub const COMMAND_CONNECT_HELLO: [u8; 20] = [
|
||||
0x10, 0x14, 0x53, 0xab, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
|
||||
pub struct AtemPacket<T: AsRef<[u8]>> {
|
||||
buf: T,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AtemPacketErr {
|
||||
TooShort(String),
|
||||
LengthDiffers(String),
|
||||
/// The packet was too short
|
||||
TooShort { got: usize },
|
||||
/// The packet's stated and actual lengths were different
|
||||
LengthDiffers { expected: u16, got: usize },
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
|
@ -32,64 +42,122 @@ impl From<PacketFlag> for u8 {
|
|||
}
|
||||
}
|
||||
|
||||
impl AtemPacket {
|
||||
impl<T: AsRef<[u8]>> AtemPacket<T> {
|
||||
pub fn new_checked(buf: T) -> Result<Self, AtemPacketErr> {
|
||||
let len = buf.as_ref().len();
|
||||
if len < 12 {
|
||||
return Err(AtemPacketErr::TooShort {
|
||||
got: buf.as_ref().len(),
|
||||
});
|
||||
}
|
||||
let p = Self { buf };
|
||||
if p.length() as usize != len {
|
||||
return Err(AtemPacketErr::LengthDiffers {
|
||||
expected: p.length(),
|
||||
got: len,
|
||||
});
|
||||
}
|
||||
Ok(p)
|
||||
}
|
||||
pub fn length(&self) -> u16 {
|
||||
self.length
|
||||
u16::from_be_bytes(self.buf.as_ref()[0..=1].try_into().unwrap()) & 0x07ff
|
||||
}
|
||||
|
||||
pub fn flags(&self) -> u8 {
|
||||
self.flags
|
||||
self.buf.as_ref()[0] >> 3
|
||||
}
|
||||
|
||||
pub fn session_id(&self) -> u16 {
|
||||
self.session_id
|
||||
u16::from_be_bytes(self.buf.as_ref()[2..=3].try_into().unwrap())
|
||||
}
|
||||
|
||||
pub fn ack_number(&self) -> u16 {
|
||||
u16::from_be_bytes(self.buf.as_ref()[4..=5].try_into().unwrap())
|
||||
}
|
||||
|
||||
pub fn remote_sequence_number(&self) -> u16 {
|
||||
u16::from_be_bytes(self.buf.as_ref()[9..=10].try_into().unwrap())
|
||||
}
|
||||
|
||||
pub fn remote_packet_id(&self) -> u16 {
|
||||
self.remote_packet_id
|
||||
}
|
||||
|
||||
pub fn body(&self) -> Vec<u8> {
|
||||
self.body.clone()
|
||||
u16::from_be_bytes(self.buf.as_ref()[10..=11].try_into().unwrap())
|
||||
}
|
||||
|
||||
/// Return true if this packet has the given [`PacketFlag`]
|
||||
pub fn has_flag(&self, flag: PacketFlag) -> bool {
|
||||
self.flags & u8::from(flag) > 0
|
||||
}
|
||||
self.flags() & u8::from(flag) > 0
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for AtemPacket {
|
||||
type Error = AtemPacketErr;
|
||||
|
||||
fn try_from(buffer: &[u8]) -> Result<Self, Self::Error> {
|
||||
if buffer.len() < 12 {
|
||||
return Err(AtemPacketErr::TooShort(format!(
|
||||
"Invalid packet from ATEM {:x?}",
|
||||
buffer
|
||||
)));
|
||||
}
|
||||
|
||||
let length = u16::from_be_bytes(buffer[0..2].try_into().unwrap()) & 0x07ff;
|
||||
if length as usize != buffer.len() {
|
||||
return Err(AtemPacketErr::LengthDiffers(format!(
|
||||
"Length of message differs, expected {} got {}",
|
||||
length,
|
||||
buffer.len()
|
||||
)));
|
||||
}
|
||||
|
||||
let flags = buffer[0] >> 3;
|
||||
let session_id = u16::from_be_bytes(buffer[2..4].try_into().unwrap());
|
||||
let remote_packet_id = u16::from_be_bytes(buffer[10..12].try_into().unwrap());
|
||||
|
||||
let body = buffer[12..].to_vec();
|
||||
|
||||
Ok(AtemPacket {
|
||||
length,
|
||||
flags,
|
||||
session_id,
|
||||
remote_packet_id,
|
||||
body,
|
||||
/// Get an iterator over the `Field`s in this packet.
|
||||
///
|
||||
/// Returns None if this is a packet without fields.
|
||||
pub fn fields(&self) -> Option<Fields> {
|
||||
// TODO: do we only ever get newsessionid during the handshake (i.e. not in a packet with fields)?
|
||||
if self.has_flag(PacketFlag::NewSessionId) {
|
||||
None
|
||||
} else {
|
||||
Some(Fields {
|
||||
data: self.body(),
|
||||
offset: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn body(&self) -> &[u8] {
|
||||
&self.buf.as_ref()[12..]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<T: AsRef<[u8]>> Display for AtemPacket<T> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"sid: {}, len: {}, fields: ({})",
|
||||
self.session_id(),
|
||||
self.length(),
|
||||
self.fields()
|
||||
.and_then(|f| Some(
|
||||
f.map(|f| str::from_utf8(f.r#type).unwrap())
|
||||
.intersperse(", ")
|
||||
.collect::<String>()
|
||||
))
|
||||
.unwrap_or("none".into())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// An ATEM protocol field - a 4-ascii character type plus variable length data
|
||||
pub struct Field<'a> {
|
||||
r#type: &'a [u8; 4],
|
||||
data: &'a [u8],
|
||||
}
|
||||
|
||||
/// Created by [`AtemPacket::fields`]
|
||||
pub struct Fields<'a> {
|
||||
data: &'a [u8],
|
||||
offset: usize,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for Fields<'a> {
|
||||
type Item = Field<'a>;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let remain = self.data.len() - self.offset;
|
||||
if remain == 0 {
|
||||
return None;
|
||||
} else if remain < 8 {
|
||||
// TODO: is 8 indeed the minimum size for something here? (i.e. no field data)
|
||||
panic!("Oh no");
|
||||
}
|
||||
|
||||
let length =
|
||||
u16::from_be_bytes(self.data[self.offset..=self.offset + 1].try_into().unwrap());
|
||||
// TODO: sanity check length
|
||||
let r#type: &[u8; 4] = self.data[self.offset + 4..=self.offset + 7]
|
||||
.try_into()
|
||||
.unwrap();
|
||||
let data = &self.data[self.offset + 8..self.offset + (length as usize)];
|
||||
self.offset += (length as usize);
|
||||
Some(Field { r#type, data })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use core::net::SocketAddr;
|
||||
use std::{io, sync::Arc, thread::yield_now};
|
||||
|
||||
use tokio::{sync::RwLock, task::JoinHandle};
|
||||
|
@ -17,8 +18,8 @@ pub enum AtemSocketConnectionError {
|
|||
}
|
||||
|
||||
impl AtemSocket {
|
||||
pub fn new() -> Self {
|
||||
let socket = AtemSocketInner::new();
|
||||
pub fn new<A: Into<SocketAddr>>(addr: A) -> Self {
|
||||
let socket = AtemSocketInner::new(addr.into());
|
||||
let socket = Arc::new(RwLock::new(socket));
|
||||
|
||||
let socket_clone = Arc::clone(&socket);
|
||||
|
@ -37,12 +38,8 @@ impl AtemSocket {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn connect(
|
||||
&mut self,
|
||||
address: String,
|
||||
port: u16,
|
||||
) -> Result<(), AtemSocketConnectionError> {
|
||||
self.socket.write().await.connect(address, port).await?;
|
||||
pub async fn connect(&mut self) -> Result<(), AtemSocketConnectionError> {
|
||||
self.socket.write().await.connect().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -60,9 +57,3 @@ impl AtemSocket {
|
|||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AtemSocket {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
use core::net::SocketAddr;
|
||||
use std::{
|
||||
io,
|
||||
net::SocketAddr,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
|
||||
use log::debug;
|
||||
use log::{debug, trace};
|
||||
use tokio::net::UdpSocket;
|
||||
|
||||
use crate::atem_lib::atem_util;
|
||||
use crate::atem_lib::atem_packet::{AtemPacket, PacketFlag, COMMAND_CONNECT_HELLO};
|
||||
|
||||
const IN_FLIGHT_TIMEOUT: u64 = 60;
|
||||
const CONNECTION_TIMEOUT: u64 = 5000;
|
||||
|
@ -39,27 +39,6 @@ impl Into<u8> for ConnectionState {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum PacketFlag {
|
||||
AckRequest,
|
||||
NewSessionId,
|
||||
IsRetransmit,
|
||||
RetransmitRequest,
|
||||
AckReply,
|
||||
}
|
||||
|
||||
impl From<PacketFlag> for u8 {
|
||||
fn from(flag: PacketFlag) -> Self {
|
||||
match flag {
|
||||
PacketFlag::AckRequest => 0x01,
|
||||
PacketFlag::NewSessionId => 0x02,
|
||||
PacketFlag::IsRetransmit => 0x04,
|
||||
PacketFlag::RetransmitRequest => 0x08,
|
||||
PacketFlag::AckReply => 0x10,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct InFlightPacket {
|
||||
packet_id: u16,
|
||||
|
@ -89,8 +68,7 @@ pub struct AtemSocketInner {
|
|||
session_id: u16,
|
||||
|
||||
socket: Option<UdpSocket>,
|
||||
address: String,
|
||||
port: u16,
|
||||
address: SocketAddr,
|
||||
|
||||
last_received_at: SystemTime,
|
||||
last_received_packed_id: u16,
|
||||
|
@ -113,7 +91,7 @@ enum AtemSocketWriteError {
|
|||
}
|
||||
|
||||
impl AtemSocketInner {
|
||||
pub fn new() -> Self {
|
||||
pub fn new(addr: SocketAddr) -> Self {
|
||||
AtemSocketInner {
|
||||
connection_state: ConnectionState::Closed,
|
||||
reconnect_timer: None,
|
||||
|
@ -123,8 +101,7 @@ impl AtemSocketInner {
|
|||
session_id: 0,
|
||||
|
||||
socket: None,
|
||||
address: "0.0.0.0".to_string(),
|
||||
port: 0,
|
||||
address: addr,
|
||||
|
||||
last_received_at: SystemTime::now(),
|
||||
last_received_packed_id: 0,
|
||||
|
@ -134,15 +111,9 @@ impl AtemSocketInner {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn connect(&mut self, address: String, port: u16) -> Result<(), io::Error> {
|
||||
self.address = address.clone();
|
||||
self.port = port;
|
||||
|
||||
pub async fn connect(&mut self) -> Result<(), io::Error> {
|
||||
let socket = UdpSocket::bind("0.0.0.0:0").await?;
|
||||
let remote_addr = format!("{}:{}", address, port)
|
||||
.parse::<SocketAddr>()
|
||||
.unwrap();
|
||||
socket.connect(remote_addr).await?;
|
||||
socket.connect(self.address).await?;
|
||||
self.socket = Some(socket);
|
||||
|
||||
self.start_timers();
|
||||
|
@ -152,7 +123,7 @@ impl AtemSocketInner {
|
|||
self.in_flight = vec![];
|
||||
debug!("Reconnect");
|
||||
|
||||
self.send_packet(&atem_util::COMMAND_CONNECT_HELLO).await;
|
||||
self.send_packet(&COMMAND_CONNECT_HELLO).await;
|
||||
self.connection_state = ConnectionState::SynSent;
|
||||
|
||||
Ok(())
|
||||
|
@ -218,7 +189,7 @@ impl AtemSocketInner {
|
|||
|
||||
async fn restart_connection(&mut self) {
|
||||
self.disconnect();
|
||||
self.connect(self.address.clone(), self.port).await.ok();
|
||||
self.connect().await.ok();
|
||||
}
|
||||
|
||||
pub async fn tick(&mut self) {
|
||||
|
@ -277,30 +248,23 @@ impl AtemSocketInner {
|
|||
}
|
||||
|
||||
async fn recieved_packet(&mut self, packet: &[u8]) {
|
||||
debug!("RECV {:x?}", packet);
|
||||
trace!("RX Raw: {:x?}", packet);
|
||||
|
||||
if packet.len() < 12 {
|
||||
debug!("Invalid packet from ATEM {:x?}", packet);
|
||||
let checked = match AtemPacket::new_checked(packet) {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
debug!("Invalid packet ({:?}): {:x?}", e, packet);
|
||||
return;
|
||||
}
|
||||
};
|
||||
debug!("RX: {}", checked);
|
||||
|
||||
self.last_received_at = SystemTime::now();
|
||||
let length = u16::from_be_bytes(packet[0..2].try_into().unwrap()) & 0x07ff;
|
||||
|
||||
if length as usize != packet.len() {
|
||||
debug!(
|
||||
"Length of message differs, expected {} got {}",
|
||||
length,
|
||||
packet.len()
|
||||
);
|
||||
return;
|
||||
}
|
||||
self.session_id = checked.session_id();
|
||||
let remote_packet_id = checked.remote_packet_id();
|
||||
|
||||
let flags = packet[0] >> 3;
|
||||
self.session_id = u16::from_be_bytes(packet[2..4].try_into().unwrap());
|
||||
let remote_packet_id = u16::from_be_bytes(packet[10..12].try_into().unwrap());
|
||||
|
||||
if flags & u8::from(PacketFlag::NewSessionId) > 0 {
|
||||
if checked.flags() & u8::from(PacketFlag::NewSessionId) > 0 {
|
||||
debug!("New session");
|
||||
self.connection_state = ConnectionState::Established;
|
||||
self.last_received_packed_id = remote_packet_id;
|
||||
|
@ -309,20 +273,20 @@ impl AtemSocketInner {
|
|||
}
|
||||
|
||||
if self.connection_state == ConnectionState::Established {
|
||||
if flags & u8::from(PacketFlag::RetransmitRequest) > 0 {
|
||||
if checked.has_flag(PacketFlag::RetransmitRequest) {
|
||||
let from_packet_id = u16::from_be_bytes(packet[6..8].try_into().unwrap());
|
||||
debug!("Retransmit request: {:x?}", from_packet_id);
|
||||
|
||||
self.retransmit_from(from_packet_id).await;
|
||||
}
|
||||
|
||||
if flags & u8::from(PacketFlag::AckRequest) > 0 {
|
||||
if checked.has_flag(PacketFlag::AckRequest) {
|
||||
if remote_packet_id == (self.last_received_packed_id + 1) % MAX_PACKET_ID {
|
||||
self.last_received_packed_id = remote_packet_id;
|
||||
self.send_or_queue_ack().await;
|
||||
|
||||
if length > 12 {
|
||||
self.on_command_received(&packet[12..], remote_packet_id);
|
||||
if checked.length() > 12 {
|
||||
self.on_command_received(checked.body(), remote_packet_id);
|
||||
}
|
||||
} else if self
|
||||
.is_packet_covered_by_ack(self.last_received_packed_id, remote_packet_id)
|
||||
|
@ -331,12 +295,12 @@ impl AtemSocketInner {
|
|||
}
|
||||
}
|
||||
|
||||
if flags & u8::from(PacketFlag::IsRetransmit) > 0 {
|
||||
if checked.has_flag(PacketFlag::IsRetransmit) {
|
||||
debug!("ATEM retransmitted packet {:x?}", remote_packet_id);
|
||||
}
|
||||
|
||||
if flags & u8::from(PacketFlag::AckReply) > 0 {
|
||||
let ack_packet_id = u16::from_be_bytes(packet[4..6].try_into().unwrap());
|
||||
if checked.has_flag(PacketFlag::AckReply) {
|
||||
let ack_packet_id = checked.ack_number();
|
||||
let mut acked_commands: Vec<AckedPacket> = vec![];
|
||||
|
||||
self.in_flight = self
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
pub const COMMAND_CONNECT_HELLO: [u8; 20] = [
|
||||
0x10, 0x14, 0x53, 0xab, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
];
|
|
@ -1,4 +1,3 @@
|
|||
mod atem_packet;
|
||||
pub mod atem_packet;
|
||||
pub mod atem_socket;
|
||||
mod atem_socket_inner;
|
||||
pub mod atem_util;
|
||||
|
|
|
@ -2,5 +2,5 @@
|
|||
pub struct ColorGeneratorState {
|
||||
pub hue: u64,
|
||||
pub saturation: u64,
|
||||
pub luma: u64
|
||||
pub luma: u64,
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use crate::enums::{ExternalPortType, FairlightAnalogInputLevel, FairlightAudioMixOption, FairlightAudioSourceType, FairlightInputConfiguration, FairlightInputType};
|
||||
use crate::enums::{
|
||||
ExternalPortType, FairlightAnalogInputLevel, FairlightAudioMixOption, FairlightAudioSourceType,
|
||||
FairlightInputConfiguration, FairlightInputType,
|
||||
};
|
||||
|
||||
#[derive(Getters, new)]
|
||||
pub struct FairlightAudioDynamicsState {
|
||||
|
@ -8,7 +11,7 @@ pub struct FairlightAudioDynamicsState {
|
|||
|
||||
pub limiter: Option<FairlightAudioLimiterState>,
|
||||
pub compressor: Option<FairlightAudioCompressorState>,
|
||||
pub expander: Option<FairlightAudioExpanderState>
|
||||
pub expander: Option<FairlightAudioExpanderState>,
|
||||
}
|
||||
|
||||
#[derive(Getters, new)]
|
||||
|
@ -17,7 +20,7 @@ pub struct FairlightAudioLimiterState {
|
|||
pub threshold: u64,
|
||||
pub attack: u64,
|
||||
pub hold: u64,
|
||||
pub release: u64
|
||||
pub release: u64,
|
||||
}
|
||||
|
||||
#[derive(Getters, new)]
|
||||
|
@ -27,7 +30,7 @@ pub struct FairlightAudioCompressorState {
|
|||
pub ratio: u64,
|
||||
pub attack: u64,
|
||||
pub hold: u64,
|
||||
pub release: u64
|
||||
pub release: u64,
|
||||
}
|
||||
|
||||
#[derive(Getters, new)]
|
||||
|
@ -39,7 +42,7 @@ pub struct FairlightAudioExpanderState {
|
|||
pub ratio: u64,
|
||||
pub attack: u64,
|
||||
pub hold: u64,
|
||||
pub release: u64
|
||||
pub release: u64,
|
||||
}
|
||||
|
||||
#[derive(Getters, new)]
|
||||
|
@ -55,14 +58,14 @@ pub struct FairlightAudioEqualizerBandState {
|
|||
pub frequency: u64,
|
||||
|
||||
pub gain: u64,
|
||||
pub q_factor: u64
|
||||
pub q_factor: u64,
|
||||
}
|
||||
|
||||
#[derive(Getters, new)]
|
||||
pub struct FairlightAudioMasterChannelPropertiesState {
|
||||
// Gain in decibel, -Infinity to +6dB
|
||||
pub fader_gain: u64,
|
||||
pub follow_fade_to_black: bool
|
||||
pub follow_fade_to_black: bool,
|
||||
}
|
||||
|
||||
#[derive(Getters, new)]
|
||||
|
@ -78,21 +81,21 @@ pub struct FairlightAudioMonitorChannel {
|
|||
pub gain: u64,
|
||||
pub input_master_gain: u64,
|
||||
pub input_talkback_gain: u64,
|
||||
pub input_sidetone_gain: u64
|
||||
pub input_sidetone_gain: u64,
|
||||
}
|
||||
|
||||
#[derive(Getters, new)]
|
||||
pub struct FairlightAudioSource {
|
||||
pub properties: Option<FairlightAudioSourcePropertiesState>,
|
||||
pub equalizer: Option<FairlightAudioEqualizerState>,
|
||||
pub dynamics: Option<FairlightAudioDynamicsState>
|
||||
pub dynamics: Option<FairlightAudioDynamicsState>,
|
||||
}
|
||||
|
||||
#[derive(Getters, new)]
|
||||
pub struct FairlightAudioEqualizerState {
|
||||
pub enabled: bool,
|
||||
pub gain: u64,
|
||||
bands: Vec<FairlightAudioEqualizerBandState>
|
||||
bands: Vec<FairlightAudioEqualizerBandState>,
|
||||
}
|
||||
|
||||
#[derive(Getters, new)]
|
||||
|
@ -110,13 +113,13 @@ pub struct FairlightAudioSourcePropertiesState {
|
|||
pub fader_gain: u64,
|
||||
|
||||
supported_mix_options: Vec<FairlightAudioMixOption>,
|
||||
pub mix_option: FairlightAudioMixOption
|
||||
pub mix_option: FairlightAudioMixOption,
|
||||
}
|
||||
|
||||
#[derive(Getters, new)]
|
||||
pub struct FairlightAudioInput {
|
||||
pub properties: Option<FairlightAudioInputProperties>,
|
||||
pub sources: HashMap<String, FairlightAudioSource>
|
||||
pub sources: HashMap<String, FairlightAudioSource>,
|
||||
}
|
||||
|
||||
#[derive(Getters, new)]
|
||||
|
@ -128,7 +131,7 @@ pub struct FairlightAudioInputProperties {
|
|||
pub active_configuration: FairlightInputConfiguration,
|
||||
|
||||
supported_input_levels: Vec<FairlightAnalogInputLevel>,
|
||||
pub activeInputLevel: FairlightAnalogInputLevel
|
||||
pub activeInputLevel: FairlightAnalogInputLevel,
|
||||
}
|
||||
|
||||
#[derive(Getters, new)]
|
||||
|
@ -137,5 +140,5 @@ pub struct AtemFairlightAudioState {
|
|||
pub master: Option<FairlightAudioMasterChannel>,
|
||||
pub monitor: Option<FairlightAudioMonitorChannel>,
|
||||
|
||||
pub audio_follow_video_crossfade_transition_enabled: Option<bool>
|
||||
pub audio_follow_video_crossfade_transition_enabled: Option<bool>,
|
||||
}
|
||||
|
|
|
@ -10,5 +10,5 @@ pub struct InputChannel {
|
|||
pub external_port_type: ExternalPortType,
|
||||
internal_port_type: InternalPortType,
|
||||
source_availability: SourceAvailability,
|
||||
me_availability: MeAvailability
|
||||
me_availability: MeAvailability,
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ pub struct RecordingState {
|
|||
|
||||
pub duration: Option<Timecode>,
|
||||
|
||||
pub disks: HashMap<u64, RecordingDiskProperties>
|
||||
pub disks: HashMap<u64, RecordingDiskProperties>,
|
||||
}
|
||||
|
||||
#[derive(Getters, new)]
|
||||
|
@ -19,7 +19,7 @@ pub struct RecordingDiskProperties {
|
|||
pub disk_id: u64,
|
||||
pub volume_name: String,
|
||||
pub recording_time_available: u64,
|
||||
pub status: RecordingDiskStatus
|
||||
pub status: RecordingDiskStatus,
|
||||
}
|
||||
|
||||
#[derive(Getters, new)]
|
||||
|
@ -27,7 +27,7 @@ pub struct RecordingStateStatus {
|
|||
pub state: RecordingStatus,
|
||||
pub error: RecordingError,
|
||||
|
||||
pub recording_time_available: u64
|
||||
pub recording_time_available: u64,
|
||||
}
|
||||
|
||||
#[derive(Getters, new)]
|
||||
|
@ -37,5 +37,5 @@ pub struct RecordingStateProperties {
|
|||
pub working_set_1_disk_id: u64,
|
||||
pub working_set_2_disk_id: u64,
|
||||
|
||||
pub record_in_all_cameras: bool
|
||||
pub record_in_all_cameras: bool,
|
||||
}
|
||||
|
|
|
@ -8,19 +8,19 @@ pub struct StreamingState {
|
|||
pub stats: Option<StreamingStateStats>,
|
||||
pub service: StreamingServiceProperties,
|
||||
|
||||
pub duration: Option<Timecode>
|
||||
pub duration: Option<Timecode>,
|
||||
}
|
||||
|
||||
#[derive(Getters, new)]
|
||||
pub struct StreamingStateStatus {
|
||||
state: StreamingStatus,
|
||||
error: StreamingError
|
||||
error: StreamingError,
|
||||
}
|
||||
|
||||
#[derive(Getters, new)]
|
||||
pub struct StreamingStateStats {
|
||||
cache_used: u64,
|
||||
encoding_bitrate: u64
|
||||
encoding_bitrate: u64,
|
||||
}
|
||||
|
||||
pub struct StreamingServiceProperties {
|
||||
|
@ -28,5 +28,5 @@ pub struct StreamingServiceProperties {
|
|||
pub url: String,
|
||||
pub key: String,
|
||||
|
||||
bitrates: (u64, u64)
|
||||
bitrates: (u64, u64),
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::time::Duration;
|
||||
use std::{env::args, net::Ipv4Addr, time::Duration};
|
||||
|
||||
use atem_connection_rs::{
|
||||
atem_lib::atem_socket::AtemSocket,
|
||||
|
@ -18,8 +18,11 @@ async fn main() {
|
|||
let switch_to_source_1 = ProgramInput::new(0, 1);
|
||||
let switch_to_source_2 = ProgramInput::new(0, 2);
|
||||
|
||||
let mut atem = AtemSocket::new();
|
||||
atem.connect("127.0.0.1".to_string(), 9910).await.ok();
|
||||
let mut atem = AtemSocket::new((
|
||||
args().skip(1).next().unwrap().parse::<Ipv4Addr>().unwrap(),
|
||||
9910,
|
||||
));
|
||||
atem.connect().await.ok();
|
||||
|
||||
let mut tracking_id = 0;
|
||||
loop {
|
||||
|
|
150
flake.lock
150
flake.lock
|
@ -1,70 +1,15 @@
|
|||
{
|
||||
"nodes": {
|
||||
"devshell": {
|
||||
"naersk": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1705332421,
|
||||
"narHash": "sha256-USpGLPme1IuqG78JNqSaRabilwkCyHmVWY0M9vYyqEA=",
|
||||
"owner": "numtide",
|
||||
"repo": "devshell",
|
||||
"rev": "83cb93d6d063ad290beee669f4badf9914cc16ec",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "devshell",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1701680307,
|
||||
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"inputs": {
|
||||
"systems": "systems_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1681202837,
|
||||
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"naersk": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1698420672,
|
||||
"narHash": "sha256-/TdeHMPRjjdJub7p7+w55vyABrsJlt5QkznPYy55vKA=",
|
||||
"lastModified": 1745925850,
|
||||
"narHash": "sha256-cyAAMal0aPrlb1NgzMxZqeN1mAJ2pJseDhm2m6Um8T0=",
|
||||
"owner": "nix-community",
|
||||
"repo": "naersk",
|
||||
"rev": "aeb58d5e8faead8980a807c840232697982d47b9",
|
||||
"rev": "38bc60bbc157ae266d4a0c96671c6c742ee17a5f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -75,11 +20,11 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1704161960,
|
||||
"narHash": "sha256-QGua89Pmq+FBAro8NriTuoO/wNaUtugt29/qqA8zeeM=",
|
||||
"lastModified": 1749401433,
|
||||
"narHash": "sha256-HXIQzULIG/MEUW2Q/Ss47oE3QrjxvpUX7gUl4Xp6lnc=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "63143ac2c9186be6d9da6035fa22620018c85932",
|
||||
"rev": "08fcb0dcb59df0344652b38ea6326a2d8271baff",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -91,12 +36,11 @@
|
|||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1705883077,
|
||||
"narHash": "sha256-ByzHHX3KxpU1+V0erFy8jpujTufimh6KaS/Iv3AciHk=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "5f5210aa20e343b7e35f40c033000db0ef80d7b9",
|
||||
"type": "github"
|
||||
"lastModified": 1733412085,
|
||||
"narHash": "sha256-FillH0qdWDt/nlO6ED7h4cmN+G9uXwGjwmCnHs0QVYM=",
|
||||
"path": "/nix/store/5wvrlpfrwlmg24ywkybvidyzcvki6bwx-source",
|
||||
"rev": "4dc2fc4e62dbf62b84132fe526356fbac7b03541",
|
||||
"type": "path"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
|
@ -105,25 +49,11 @@
|
|||
},
|
||||
"nixpkgs_3": {
|
||||
"locked": {
|
||||
"lastModified": 1705883077,
|
||||
"narHash": "sha256-ByzHHX3KxpU1+V0erFy8jpujTufimh6KaS/Iv3AciHk=",
|
||||
"lastModified": 1744536153,
|
||||
"narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "5f5210aa20e343b7e35f40c033000db0ef80d7b9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"nixpkgs_4": {
|
||||
"locked": {
|
||||
"lastModified": 1681358109,
|
||||
"narHash": "sha256-eKyxW4OohHQx9Urxi7TQlFBTDWII+F+x2hklDOQPB50=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "96ba1c52e54e74c3197f4d43026b3f3d92e83ff9",
|
||||
"rev": "18dd725c29603f582cf1900e0d25f9f1063dbf11",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -135,24 +65,22 @@
|
|||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"devshell": "devshell",
|
||||
"naersk": "naersk",
|
||||
"nixpkgs": "nixpkgs_3",
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"rust-overlay": "rust-overlay",
|
||||
"utils": "utils"
|
||||
}
|
||||
},
|
||||
"rust-overlay": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils_2",
|
||||
"nixpkgs": "nixpkgs_4"
|
||||
"nixpkgs": "nixpkgs_3"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1705976279,
|
||||
"narHash": "sha256-Zx97bJ3+O8IP70uJPD//rRsr8bcxICISMTZUT/L9eFk=",
|
||||
"lastModified": 1749436897,
|
||||
"narHash": "sha256-OkDtaCGQQVwVFz5HWfbmrMJR99sFIMXHCHEYXzUJEJY=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "f889dc31ef97835834bdc3662394ebdb3c96b974",
|
||||
"rev": "e7876c387e35dc834838aff254d8e74cf5bd4f19",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -176,46 +104,16 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_2": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_3": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"utils": {
|
||||
"inputs": {
|
||||
"systems": "systems_3"
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1705309234,
|
||||
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
14
flake.nix
14
flake.nix
|
@ -3,7 +3,6 @@
|
|||
|
||||
inputs = {
|
||||
utils.url = "github:numtide/flake-utils";
|
||||
devshell.url = "github:numtide/devshell";
|
||||
naersk.url = "github:nix-community/naersk";
|
||||
rust-overlay.url = "github:oxalica/rust-overlay";
|
||||
};
|
||||
|
@ -13,7 +12,6 @@
|
|||
nixpkgs,
|
||||
utils,
|
||||
naersk,
|
||||
devshell,
|
||||
rust-overlay,
|
||||
}:
|
||||
utils.lib.eachDefaultSystem (system: let
|
||||
|
@ -35,16 +33,10 @@
|
|||
|
||||
apps.default = utils.lib.mkApp {drv = packages.default;};
|
||||
|
||||
# Provide a dev env with rust and rust-analyzer
|
||||
devShells.default = let
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
overlays = [devshell.overlays.default];
|
||||
};
|
||||
in
|
||||
pkgs.devshell.mkShell {
|
||||
devShells.default = pkgs.mkShell {
|
||||
packages = with pkgs; [(rust.override {extensions = ["rust-src"];}) rust-analyzer gcc];
|
||||
|
||||
};
|
||||
formatter = pkgs.alejandra;
|
||||
formatter = pkgs.nixfmt-rfc-style;
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue