feat: The rest of the owl
This commit is contained in:
parent
8cce21193a
commit
be96c86f74
4
build.rs
4
build.rs
|
@ -44,6 +44,10 @@ fn main() {
|
||||||
panic!("Running `trunk build` failed, reason {err}");
|
panic!("Running `trunk build` failed, reason {err}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Ok(trunk_output_string) = String::from_utf8(trunk_build_output.stdout) {
|
||||||
|
println!("{trunk_output_string}");
|
||||||
|
}
|
||||||
|
|
||||||
let dist_paths = fs::read_dir("./frontend/dist").expect("Could not read dist directory");
|
let dist_paths = fs::read_dir("./frontend/dist").expect("Could not read dist directory");
|
||||||
let paths: Vec<Result<DirEntry, Error>> = dist_paths.collect();
|
let paths: Vec<Result<DirEntry, Error>> = dist_paths.collect();
|
||||||
let paths: Vec<(String, PathBuf)> = paths
|
let paths: Vec<(String, PathBuf)> = paths
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::VecDeque,
|
collections::{HashMap, VecDeque},
|
||||||
fmt::Display,
|
|
||||||
mem,
|
mem,
|
||||||
net::{IpAddr, Ipv4Addr},
|
net::{IpAddr, Ipv4Addr},
|
||||||
};
|
};
|
||||||
|
|
||||||
use egui::{Button, Color32, RichText, Sense, Stroke, Vec2};
|
use egui::{Button, Color32, RichText, Rounding, Sense, Stroke, Vec2};
|
||||||
use ewebsock::{Options, WsReceiver, WsSender};
|
use ewebsock::{Options, WsReceiver, WsSender};
|
||||||
use wasm_timer::Instant;
|
use wasm_timer::Instant;
|
||||||
|
|
||||||
use crate::websocket::{
|
use crate::websocket::{
|
||||||
AddHyperdeckRequest, ClientRequest, HyperdeckConnectionState, RemoveHyperdeckRequest,
|
AddHyperdeckRequest, ClientRequest, HyperdeckConnectionState, HyperdeckRecordBay,
|
||||||
ServerEvent,
|
RecordingState, RemoveHyperdeckRequest, ServerEvent,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct HyperdeckMonitorApp {
|
pub struct HyperdeckMonitorApp {
|
||||||
|
@ -37,28 +36,69 @@ impl Default for HyperdeckMonitorApp {
|
||||||
new_hyperdeck_name: "".to_owned(),
|
new_hyperdeck_name: "".to_owned(),
|
||||||
new_hyperdeck_port: 9993.to_string(),
|
new_hyperdeck_port: 9993.to_string(),
|
||||||
hyperdecks: vec![
|
hyperdecks: vec![
|
||||||
// Hyperdeck {
|
Hyperdeck {
|
||||||
// id: "test-1".to_string(),
|
id: "test-1".to_string(),
|
||||||
// name: "Test Hyperdeck 1".to_string(),
|
name: "Description: Connected Hyperdeck - Not Recording - Not Much Time"
|
||||||
// ip: IpAddr::V4(Ipv4Addr::new(192, 168, 10, 1)),
|
.to_string(),
|
||||||
// status: HyperdeckStatus::Connected,
|
ip: IpAddr::V4(Ipv4Addr::new(192, 168, 10, 1)),
|
||||||
// recording_bays: vec![HyperdeckRecordBay {
|
status: HyperdeckStatus::Connected,
|
||||||
// status: RecordingStatus::NotRecording,
|
recording_status: RecordingState::NotRecording,
|
||||||
// storage_capacity_mb: 500_000,
|
slots: vec![(
|
||||||
// recording_time_remaining: TimeRemaining(60),
|
0usize,
|
||||||
// }],
|
HyperdeckRecordBay {
|
||||||
// },
|
recording_time_remaining: 60,
|
||||||
// Hyperdeck {
|
},
|
||||||
// id: "test-2".to_string(),
|
)]
|
||||||
// name: "Test Hyperdeck 2".to_string(),
|
.into_iter()
|
||||||
// ip: IpAddr::V4(Ipv4Addr::new(192, 168, 10, 2)),
|
.collect::<HashMap<usize, HyperdeckRecordBay>>(),
|
||||||
// status: HyperdeckStatus::Disconnected,
|
},
|
||||||
// recording_bays: vec![HyperdeckRecordBay {
|
Hyperdeck {
|
||||||
// status: RecordingStatus::NotRecording,
|
id: "test-2".to_string(),
|
||||||
// storage_capacity_mb: 500_000,
|
name: "Description: Connected Hyperdeck - Recording - Not Much Time"
|
||||||
// recording_time_remaining: TimeRemaining(3600 * 5), // 5 Hours
|
.to_string(),
|
||||||
// }],
|
ip: IpAddr::V4(Ipv4Addr::new(192, 168, 10, 2)),
|
||||||
// },
|
status: HyperdeckStatus::Connected,
|
||||||
|
recording_status: RecordingState::Recording,
|
||||||
|
slots: vec![(
|
||||||
|
0usize,
|
||||||
|
HyperdeckRecordBay {
|
||||||
|
recording_time_remaining: 60,
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
.into_iter()
|
||||||
|
.collect::<HashMap<usize, HyperdeckRecordBay>>(),
|
||||||
|
},
|
||||||
|
Hyperdeck {
|
||||||
|
id: "test-3".to_string(),
|
||||||
|
name: "Description: Connected Hyperdeck - Recording - Plenty of Time"
|
||||||
|
.to_string(),
|
||||||
|
ip: IpAddr::V4(Ipv4Addr::new(192, 168, 10, 3)),
|
||||||
|
status: HyperdeckStatus::Connected,
|
||||||
|
recording_status: RecordingState::Recording,
|
||||||
|
slots: vec![(
|
||||||
|
0usize,
|
||||||
|
HyperdeckRecordBay {
|
||||||
|
recording_time_remaining: 60 * 30,
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
.into_iter()
|
||||||
|
.collect::<HashMap<usize, HyperdeckRecordBay>>(),
|
||||||
|
},
|
||||||
|
Hyperdeck {
|
||||||
|
id: "test-4".to_string(),
|
||||||
|
name: "Description: Disconnected Hyperdeck".to_string(),
|
||||||
|
ip: IpAddr::V4(Ipv4Addr::new(192, 168, 10, 4)),
|
||||||
|
status: HyperdeckStatus::Disconnected,
|
||||||
|
recording_status: RecordingState::NotRecording,
|
||||||
|
slots: vec![(
|
||||||
|
0usize,
|
||||||
|
HyperdeckRecordBay {
|
||||||
|
recording_time_remaining: 3600 * 5, // 5 Hours
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
.into_iter()
|
||||||
|
.collect::<HashMap<usize, HyperdeckRecordBay>>(),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
websocket_message_queue: VecDeque::new(),
|
websocket_message_queue: VecDeque::new(),
|
||||||
ws_sender,
|
ws_sender,
|
||||||
|
@ -87,18 +127,14 @@ impl eframe::App for HyperdeckMonitorApp {
|
||||||
name: hyperdeck.name,
|
name: hyperdeck.name,
|
||||||
ip: hyperdeck.ip.parse().unwrap(),
|
ip: hyperdeck.ip.parse().unwrap(),
|
||||||
status: hyperdeck.connection_state.into(),
|
status: hyperdeck.connection_state.into(),
|
||||||
recording_bays: vec![],
|
recording_status: hyperdeck.recording_status,
|
||||||
|
slots: hyperdeck.slots,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
|
|
||||||
egui::menu::bar(ui, |ui| {
|
|
||||||
egui::widgets::global_dark_light_mode_buttons(ui);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
add_hyperdeck_panel(
|
add_hyperdeck_panel(
|
||||||
|
@ -118,11 +154,6 @@ impl eframe::App for HyperdeckMonitorApp {
|
||||||
&mut self.websocket_message_queue,
|
&mut self.websocket_message_queue,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.with_layout(egui::Layout::bottom_up(egui::Align::LEFT), |ui| {
|
|
||||||
connection_status(ui);
|
|
||||||
egui::warn_if_debug_build(ui);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if self.last_blink_change.elapsed().as_secs() >= 1 {
|
if self.last_blink_change.elapsed().as_secs() >= 1 {
|
||||||
|
@ -195,27 +226,41 @@ fn hyperdeck_list(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if !hyperdeck.recording_bays.is_empty()
|
if matches!(hyperdeck.status, HyperdeckStatus::Connected) {
|
||||||
&& matches!(hyperdeck.status, HyperdeckStatus::Connected)
|
ui.horizontal(|ui| {
|
||||||
{
|
match hyperdeck.recording_status {
|
||||||
let recording_bays_text: RichText = "Recording Bays".into();
|
RecordingState::Recording => {
|
||||||
ui.label(recording_bays_text.size(16.0).strong());
|
let (response, painter) =
|
||||||
for (index, bay) in hyperdeck.recording_bays.iter().enumerate() {
|
ui.allocate_painter(Vec2 { x: 16.0, y: 16.0 }, Sense::hover());
|
||||||
ui.horizontal(|ui| {
|
let rect = response.rect;
|
||||||
let bay_label: RichText = format!("Bay {}", index + 1).into();
|
painter.rect(
|
||||||
ui.label(bay_label.strong());
|
rect,
|
||||||
match bay.status {
|
Rounding::ZERO,
|
||||||
RecordingStatus::Recording => ui.label("Recording"),
|
Color32::from_rgb(255, 255, 255),
|
||||||
RecordingStatus::NotRecording => ui.label("Not Recording"),
|
Stroke::NONE,
|
||||||
};
|
);
|
||||||
ui.label(format!(
|
let recording_text: RichText = "[Recording]".into();
|
||||||
"Total Storage Capacity: {}GB",
|
ui.label(
|
||||||
bay.storage_capacity_mb / 1000,
|
recording_text
|
||||||
));
|
.color(Color32::from_rgb(255, 255, 255))
|
||||||
let time_remaining_text: RichText =
|
.strong(),
|
||||||
format!("Time remaining: {}", bay.recording_time_remaining).into();
|
);
|
||||||
|
}
|
||||||
|
RecordingState::NotRecording => {
|
||||||
|
ui.label("[Not Recording]");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
if bay.recording_time_remaining.0 > 15 * 60 || !blink {
|
for (index, slot) in hyperdeck.slots.iter() {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
let slot_label: RichText = format!("Slot {}", index + 1).into();
|
||||||
|
ui.label(slot_label.strong());
|
||||||
|
|
||||||
|
let time_remaining_text: RichText =
|
||||||
|
format!("Time remaining: {}", slot.recording_time_remaining).into();
|
||||||
|
|
||||||
|
if slot.recording_time_remaining > 15 * 60 || !blink {
|
||||||
ui.label(time_remaining_text);
|
ui.label(time_remaining_text);
|
||||||
} else {
|
} else {
|
||||||
ui.label(time_remaining_text.color(Color32::RED));
|
ui.label(time_remaining_text.color(Color32::RED));
|
||||||
|
@ -228,20 +273,14 @@ fn hyperdeck_list(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn connection_status(ui: &mut egui::Ui) {
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
// TODO: Make it real
|
|
||||||
ui.label("Connected");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(serde::Deserialize, serde::Serialize)]
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
struct Hyperdeck {
|
struct Hyperdeck {
|
||||||
id: String,
|
id: String,
|
||||||
name: String,
|
name: String,
|
||||||
ip: IpAddr,
|
ip: IpAddr,
|
||||||
status: HyperdeckStatus,
|
status: HyperdeckStatus,
|
||||||
recording_bays: Vec<HyperdeckRecordBay>,
|
recording_status: RecordingState,
|
||||||
|
slots: HashMap<usize, HyperdeckRecordBay>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize, serde::Serialize)]
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
|
@ -258,28 +297,3 @@ impl From<HyperdeckConnectionState> for HyperdeckStatus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize, serde::Serialize)]
|
|
||||||
struct HyperdeckRecordBay {
|
|
||||||
status: RecordingStatus,
|
|
||||||
/// Storage capacity in MB.
|
|
||||||
storage_capacity_mb: u64,
|
|
||||||
/// Recording time available in seconds.
|
|
||||||
recording_time_remaining: TimeRemaining,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(serde::Deserialize, serde::Serialize)]
|
|
||||||
struct TimeRemaining(u64);
|
|
||||||
|
|
||||||
impl Display for TimeRemaining {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
let time = hrtime::from_sec_padded(self.0);
|
|
||||||
write!(f, "{time}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(serde::Deserialize, serde::Serialize)]
|
|
||||||
enum RecordingStatus {
|
|
||||||
Recording,
|
|
||||||
NotRecording,
|
|
||||||
}
|
|
||||||
|
|
73
index.ts
73
index.ts
|
@ -1,5 +1,6 @@
|
||||||
import { Hyperdeck, Commands } from 'hyperdeck-connection';
|
import { Hyperdeck, Commands } from 'hyperdeck-connection';
|
||||||
import WebSocket from 'ws';
|
import WebSocket from 'ws';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
interface WrappedHyperdeck {
|
interface WrappedHyperdeck {
|
||||||
ip: String,
|
ip: String,
|
||||||
|
@ -26,8 +27,11 @@ type WebSocketMessage = {
|
||||||
|
|
||||||
const wss = new WebSocket.Server({ port: 7867 });
|
const wss = new WebSocket.Server({ port: 7867 });
|
||||||
|
|
||||||
wss.on('connection', function connection(ws) {
|
const connected_clients: Map<string, WebSocket> = new Map();
|
||||||
ws.on('message', function message(data) {
|
|
||||||
|
wss.on('connection', (ws) => {
|
||||||
|
const clientId = uuidv4();
|
||||||
|
ws.on('message', (data) => {
|
||||||
try {
|
try {
|
||||||
const message = JSON.parse(data.toString()) as Partial<WebSocketMessage>;
|
const message = JSON.parse(data.toString()) as Partial<WebSocketMessage>;
|
||||||
handle_message(message)
|
handle_message(message)
|
||||||
|
@ -35,11 +39,16 @@ wss.on('connection', function connection(ws) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
ws.on('close', () => {
|
||||||
|
connected_clients.delete(clientId)
|
||||||
|
})
|
||||||
|
|
||||||
ws.send(JSON.stringify({
|
ws.send(JSON.stringify({
|
||||||
event: "log",
|
event: "log",
|
||||||
message: "Hello"
|
message: "Hello"
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
connected_clients.set(clientId, ws);
|
||||||
});
|
});
|
||||||
|
|
||||||
function exhaustiveMatch(_never: never) {
|
function exhaustiveMatch(_never: never) {
|
||||||
|
@ -68,24 +77,64 @@ function handle_message(message: Partial<WebSocketMessage>) {
|
||||||
hyperdeck: newHyperdeck
|
hyperdeck: newHyperdeck
|
||||||
});
|
});
|
||||||
|
|
||||||
newHyperdeck.on('connected', (info) => {
|
newHyperdeck.on('connected', (_info) => {
|
||||||
console.log(JSON.stringify(info))
|
notifyClients({
|
||||||
|
event: "hyperdeck_connected",
|
||||||
|
id: message.id
|
||||||
|
})
|
||||||
|
|
||||||
newHyperdeck.sendCommand(new Commands.TransportInfoCommand()).then((transportInfo) => {
|
setInterval(() => {
|
||||||
console.log(JSON.stringify(transportInfo))
|
newHyperdeck.sendCommand(new Commands.TransportInfoCommand()).then((transportInfo) => {
|
||||||
|
notifyClients({
|
||||||
|
event: "record_state",
|
||||||
|
hyperdeckId: message.id,
|
||||||
|
state: transportInfo.status,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
newHyperdeck.sendCommand(new Commands.DeviceInfoCommand()).then((info) => {
|
||||||
|
for (let index = 0; index < info.slots; index++) {
|
||||||
|
setInterval(() => {
|
||||||
|
newHyperdeck.sendCommand(new Commands.SlotInfoCommand(index)).then((slot) => {
|
||||||
|
notifyClients({
|
||||||
|
event: "record_time_remaining",
|
||||||
|
hyperdeckId: message.id,
|
||||||
|
slotId: slot.slotId,
|
||||||
|
remaining: slot.recordingTime
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
newHyperdeck.on('notify.slot', function (state) {
|
newHyperdeck.on('notify.slot', function (slot) {
|
||||||
console.log(JSON.stringify(state)) // catch the slot state change.
|
notifyClients({
|
||||||
|
event: "record_time_remaining",
|
||||||
|
hyperdeckId: message.id,
|
||||||
|
slotId: slot.slotId,
|
||||||
|
remaining: slot.recordingTime
|
||||||
|
})
|
||||||
})
|
})
|
||||||
newHyperdeck.on('notify.transport', function (state) {
|
newHyperdeck.on('notify.transport', function (state) {
|
||||||
console.log(JSON.stringify(state)) // catch the transport state change.
|
notifyClients({
|
||||||
|
event: "record_state",
|
||||||
|
hyperdeckId: message.id,
|
||||||
|
state: state.status
|
||||||
|
})
|
||||||
})
|
})
|
||||||
newHyperdeck.on('error', (err) => {
|
newHyperdeck.on('error', (err) => {
|
||||||
console.log('Hyperdeck error', JSON.stringify(err))
|
console.log('Hyperdeck error', JSON.stringify(err))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
newHyperdeck.on('disconnected', () => {
|
||||||
|
notifyClients({
|
||||||
|
event: "hyperdeck_disconnected",
|
||||||
|
id: message.id
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
newHyperdeck.connect(message.ip, message.port)
|
newHyperdeck.connect(message.ip, message.port)
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -105,3 +154,9 @@ function handle_message(message: Partial<WebSocketMessage>) {
|
||||||
exhaustiveMatch(message)
|
exhaustiveMatch(message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function notifyClients(message: object) {
|
||||||
|
connected_clients.forEach((client) => {
|
||||||
|
client.send(JSON.stringify(message))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"hyperdeck-connection": "^2.0.1",
|
"hyperdeck-connection": "^2.0.1",
|
||||||
|
"uuid": "^9.0.1",
|
||||||
"ws": "^8.17.0"
|
"ws": "^8.17.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -75,6 +76,18 @@
|
||||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/uuid": {
|
||||||
|
"version": "9.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
|
||||||
|
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
|
||||||
|
"funding": [
|
||||||
|
"https://github.com/sponsors/broofa",
|
||||||
|
"https://github.com/sponsors/ctavan"
|
||||||
|
],
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist/bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ws": {
|
"node_modules/ws": {
|
||||||
"version": "8.17.0",
|
"version": "8.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz",
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"hyperdeck-connection": "^2.0.1",
|
"hyperdeck-connection": "^2.0.1",
|
||||||
|
"uuid": "^9.0.1",
|
||||||
"ws": "^8.17.0"
|
"ws": "^8.17.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -4,7 +4,6 @@ use axum::response::Html;
|
||||||
use axum::Json;
|
use axum::Json;
|
||||||
use axum::{
|
use axum::{
|
||||||
body::Bytes,
|
body::Bytes,
|
||||||
extract::Path,
|
|
||||||
http::{header, HeaderValue, Method},
|
http::{header, HeaderValue, Method},
|
||||||
response::IntoResponse,
|
response::IntoResponse,
|
||||||
routing::get,
|
routing::get,
|
||||||
|
@ -66,7 +65,7 @@ pub async fn initialize_api(
|
||||||
|
|
||||||
let clients = state_clients.lock().await;
|
let clients = state_clients.lock().await;
|
||||||
let state_json = serde_json::to_string(&ServerEvent::HyperdeckMonitorState(
|
let state_json = serde_json::to_string(&ServerEvent::HyperdeckMonitorState(
|
||||||
hyperdeck_monitor_state.into(),
|
hyperdeck_monitor_state,
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
for (_, client) in clients.iter() {
|
for (_, client) in clients.iter() {
|
||||||
|
|
|
@ -40,6 +40,9 @@ pub struct HyperdeckState {
|
||||||
pub ip: String,
|
pub ip: String,
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
pub connection_state: HyperdeckConnectionState,
|
pub connection_state: HyperdeckConnectionState,
|
||||||
|
pub recording_status: RecordingState,
|
||||||
|
// HashMap to allow for sparse entries.
|
||||||
|
pub slots: HashMap<usize, HyperdeckRecordBay>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
@ -47,3 +50,15 @@ pub enum HyperdeckConnectionState {
|
||||||
Connected,
|
Connected,
|
||||||
Disconnected,
|
Disconnected,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum RecordingState {
|
||||||
|
Recording,
|
||||||
|
NotRecording,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct HyperdeckRecordBay {
|
||||||
|
/// Recording time available in seconds.
|
||||||
|
pub recording_time_remaining: u64,
|
||||||
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ pub async fn client_connection(
|
||||||
|
|
||||||
let current_state = state.read().await.clone();
|
let current_state = state.read().await.clone();
|
||||||
let state_json =
|
let state_json =
|
||||||
serde_json::to_string(&ServerEvent::HyperdeckMonitorState(current_state.into())).unwrap();
|
serde_json::to_string(&ServerEvent::HyperdeckMonitorState(current_state)).unwrap();
|
||||||
client_sender.send(Message::Text(state_json.clone())).ok();
|
client_sender.send(Message::Text(state_json.clone())).ok();
|
||||||
|
|
||||||
client.sender = Some(client_sender);
|
client.sender = Some(client_sender);
|
||||||
|
|
85
src/main.rs
85
src/main.rs
|
@ -1,8 +1,8 @@
|
||||||
use std::{process::Stdio, time::Duration};
|
use std::{collections::HashMap, process::Stdio, time::Duration};
|
||||||
|
|
||||||
use api::message::{
|
use api::message::{
|
||||||
AddHyperdeckRequest, ClientRequest, HyperdeckConnectionState, HyperdeckMonitorState,
|
AddHyperdeckRequest, ClientRequest, HyperdeckConnectionState, HyperdeckMonitorState,
|
||||||
HyperdeckState, RemoveHyperdeckRequest,
|
HyperdeckRecordBay, HyperdeckState, RecordingState, RemoveHyperdeckRequest,
|
||||||
};
|
};
|
||||||
use color_eyre::Report;
|
use color_eyre::Report;
|
||||||
use futures_util::{
|
use futures_util::{
|
||||||
|
@ -31,9 +31,8 @@ async fn main() {
|
||||||
|
|
||||||
let (node_ws_message_tx, node_ws_message_rx) = tokio::sync::mpsc::unbounded_channel();
|
let (node_ws_message_tx, node_ws_message_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||||
let (node_commands_tx, node_commands_rx) = tokio::sync::mpsc::unbounded_channel();
|
let (node_commands_tx, node_commands_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||||
let state = AppState::default();
|
|
||||||
let node_ws_communication =
|
let node_ws_communication =
|
||||||
talk_to_node_ws(state, node_ws_message_tx, node_commands_rx, cancel.clone()).fuse();
|
talk_to_node_ws(node_ws_message_tx, node_commands_rx, cancel.clone()).fuse();
|
||||||
|
|
||||||
let (state_tx, state_rx) = tokio::sync::broadcast::channel(1);
|
let (state_tx, state_rx) = tokio::sync::broadcast::channel(1);
|
||||||
let (client_request_tx, client_request_rx) = tokio::sync::mpsc::unbounded_channel();
|
let (client_request_tx, client_request_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||||
|
@ -67,7 +66,7 @@ async fn main() {
|
||||||
async fn run(
|
async fn run(
|
||||||
mut node_commands_tx: tokio::sync::mpsc::UnboundedSender<NodeWsCommand>,
|
mut node_commands_tx: tokio::sync::mpsc::UnboundedSender<NodeWsCommand>,
|
||||||
mut node_ws_message_rx: tokio::sync::mpsc::UnboundedReceiver<NodeWsMessageReceived>,
|
mut node_ws_message_rx: tokio::sync::mpsc::UnboundedReceiver<NodeWsMessageReceived>,
|
||||||
mut state_tx: tokio::sync::broadcast::Sender<HyperdeckMonitorState>,
|
state_tx: tokio::sync::broadcast::Sender<HyperdeckMonitorState>,
|
||||||
mut client_request_rx: tokio::sync::mpsc::UnboundedReceiver<ClientRequest>,
|
mut client_request_rx: tokio::sync::mpsc::UnboundedReceiver<ClientRequest>,
|
||||||
cancel: CancellationToken,
|
cancel: CancellationToken,
|
||||||
) {
|
) {
|
||||||
|
@ -100,7 +99,7 @@ async fn run(
|
||||||
|
|
||||||
async fn handle_message_from_node(
|
async fn handle_message_from_node(
|
||||||
msg: NodeWsMessageReceived,
|
msg: NodeWsMessageReceived,
|
||||||
node_commands_tx: &mut tokio::sync::mpsc::UnboundedSender<NodeWsCommand>,
|
_node_commands_tx: &mut tokio::sync::mpsc::UnboundedSender<NodeWsCommand>,
|
||||||
state: &mut HyperdeckMonitorState,
|
state: &mut HyperdeckMonitorState,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
match msg {
|
match msg {
|
||||||
|
@ -120,6 +119,38 @@ async fn handle_message_from_node(
|
||||||
});
|
});
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
NodeWsMessageReceived::RecordState {
|
||||||
|
hyperdeck_id,
|
||||||
|
status,
|
||||||
|
} => {
|
||||||
|
if let Some(hyperdeck) = state.hyperdecks.get_mut(&hyperdeck_id) {
|
||||||
|
hyperdeck.recording_status = if matches!(status, TransportStatus::Record) {
|
||||||
|
RecordingState::Recording
|
||||||
|
} else {
|
||||||
|
RecordingState::NotRecording
|
||||||
|
};
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NodeWsMessageReceived::RecordTimeRemaining {
|
||||||
|
hyperdeck_id,
|
||||||
|
slot_id,
|
||||||
|
remaining,
|
||||||
|
} => {
|
||||||
|
if let Some(hyperdeck) = state.hyperdecks.get_mut(&hyperdeck_id) {
|
||||||
|
hyperdeck
|
||||||
|
.slots
|
||||||
|
.entry(slot_id)
|
||||||
|
.or_insert(HyperdeckRecordBay {
|
||||||
|
recording_time_remaining: remaining,
|
||||||
|
});
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,6 +170,8 @@ async fn handle_message_from_client(
|
||||||
ip: ip.clone(),
|
ip: ip.clone(),
|
||||||
port,
|
port,
|
||||||
connection_state: api::message::HyperdeckConnectionState::Disconnected,
|
connection_state: api::message::HyperdeckConnectionState::Disconnected,
|
||||||
|
recording_status: api::message::RecordingState::NotRecording,
|
||||||
|
slots: HashMap::new(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
let _ = node_commands_tx.send(NodeWsCommand::AddHyperdeck(AddHyperdeckCommand {
|
let _ = node_commands_tx.send(NodeWsCommand::AddHyperdeck(AddHyperdeckCommand {
|
||||||
|
@ -210,9 +243,6 @@ async fn run_node_process(cancel: CancellationToken) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct AppState {}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
enum NodeWsCommand {
|
enum NodeWsCommand {
|
||||||
|
@ -223,12 +253,40 @@ enum NodeWsCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "snake_case")]
|
||||||
#[serde(tag = "event")]
|
#[serde(tag = "event")]
|
||||||
enum NodeWsMessageReceived {
|
enum NodeWsMessageReceived {
|
||||||
Log { message: String },
|
Log {
|
||||||
HyperdeckConnected { id: String },
|
message: String,
|
||||||
HypderdeckDisconnected { id: String },
|
},
|
||||||
|
HyperdeckConnected {
|
||||||
|
id: String,
|
||||||
|
},
|
||||||
|
HypderdeckDisconnected {
|
||||||
|
id: String,
|
||||||
|
},
|
||||||
|
RecordState {
|
||||||
|
hyperdeck_id: String,
|
||||||
|
status: TransportStatus,
|
||||||
|
},
|
||||||
|
RecordTimeRemaining {
|
||||||
|
hyperdeck_id: String,
|
||||||
|
slot_id: usize,
|
||||||
|
remaining: u64,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
enum TransportStatus {
|
||||||
|
Preview,
|
||||||
|
Stopped,
|
||||||
|
Play,
|
||||||
|
Forward,
|
||||||
|
Rewind,
|
||||||
|
Jog,
|
||||||
|
Shuttle,
|
||||||
|
Record,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
@ -246,7 +304,6 @@ struct RemoveHyperdeckCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn talk_to_node_ws(
|
async fn talk_to_node_ws(
|
||||||
state: AppState,
|
|
||||||
ws_message_tx: tokio::sync::mpsc::UnboundedSender<NodeWsMessageReceived>,
|
ws_message_tx: tokio::sync::mpsc::UnboundedSender<NodeWsMessageReceived>,
|
||||||
commands_rx: tokio::sync::mpsc::UnboundedReceiver<NodeWsCommand>,
|
commands_rx: tokio::sync::mpsc::UnboundedReceiver<NodeWsCommand>,
|
||||||
cancel: CancellationToken,
|
cancel: CancellationToken,
|
||||||
|
|
Loading…
Reference in New Issue