refactored video streaming module

This commit is contained in:
Valmo Trindade
2025-05-22 03:24:10 -03:00
parent 3f028b48c8
commit 7b5510698e
3 changed files with 80 additions and 76 deletions

View File

@@ -1,5 +1,6 @@
class CfgVehicles { class CfgVehicles {
class Logic; class Logic;
class Module_F : Logic class Module_F : Logic
{ {
class AttributesBase class AttributesBase
@@ -18,84 +19,54 @@ class CfgVehicles {
class AnyBrain; class AnyBrain;
}; };
}; };
class armatak_module_core; class EGVAR(server,moduleBase);
class armatak_module_video_stream_core: armatak_module_core { class GVAR(videoModule): EGVAR(server,moduleBase) {
scope = 2; scope = 2;
displayname = "ARMATAK MediaMTX Video Feed Parser"; scopeCurator = 0;
icon = "\a3\Modules_F_Curator\Data\iconcuratorsetcamera_ca.paa"; displayname = "Video Streaming Handler";
category = "armatak_module_category"; icon = "\a3\Modules_F_Curator\Data\iconRadio_ca.paa";
function = "armatak_fnc_video_init"; category = QEGVAR(main,moduleCategory);
function = QFUNC(videoParser);
functionPriority = 1; functionPriority = 1;
isGlobal = 0; isGlobal = 0;
isTriggerActivated = 0; isTriggerActivated = 1;
isDisposable = 1; isDisposable = 1;
is3den = 0; is3den = 0;
curatorCanAttach = 0; curatorCanAttach = 0;
curatorInfoType = "RscDisplayAttributeModuleNuke"; curatorInfoType = "RscDisplayAttributeModuleNuke";
canSetArea = 0; canSetArea = 0;
canSetAreaShape = 0; canSetAreaShape = 0;
canSetAreaHeight = 0; canSetAreaHeight = 0;
class AttributesValues {
size3[] = { 1, 1, -1 };
isRectangle = 0;
};
class Attributes: AttributesBase { class Attributes: AttributesBase {
class armatak_module_mediamtx_video_stream_instance_address: Edit { class GVAR(instanceAddress): Edit {
property = "armatak_module_mediamtx_video_stream_instance_address"; property = QGVAR(instanceAddress);
displayname = "MediaMTX Provider Address"; displayname = "MediaMTX Provider Address";
tooltip = "MediaMTX Provider Instance Address"; tooltip = "MediaMTX Provider Instance Address";
typeName = "STRING"; typeName = "STRING";
defaultValue = "localhost"; defaultValue = "localhost";
}; };
class armatak_module_mediamtx_video_stream_instance_port: Edit { class GVAR(instancePort): Edit {
property = "armatak_module_mediamtx_video_stream_instance_port"; property = "armatak_module_mediamtx_video_stream_instance_port";
displayname = "MediaMTX Provider Port"; displayname = "MediaMTX Provider Port";
tooltip = "MediaMTX Provider Port for handling video streams"; tooltip = "MediaMTX Provider Port for handling video streams";
typeName = "STRING"; typeName = "STRING";
defaultValue = "8554"; defaultValue = "8554";
}; };
class armatak_module_mediamtx_video_stream_instance_auth_user: Edit { class GVAR(instanceAuthUser): Edit {
property = "armatak_module_mediamtx_video_stream_instance_auth_user"; property = QGVAR(instanceAuthUser);
displayname = "MediaMTX Provider Username"; displayname = "MediaMTX Provider Username";
tooltip = "MediaMTX Provider Instance Username"; tooltip = "MediaMTX Provider Instance Username";
typeName = "STRING"; typeName = "STRING";
defaultValue = "administrator"; defaultValue = "administrator";
}; };
class armatak_module_mediamtx_video_stream_instance_auth_pass: Edit { class GVAR(instanceAuthPassword): Edit {
property = "armatak_module_mediamtx_video_stream_instance_auth_pass"; property = QGVAR(instanceAuthPassword);
displayname = "MediaMTX Provider Password"; displayname = "MediaMTX Provider Password";
tooltip = "MediaMTX Provider Instance Password"; tooltip = "MediaMTX Provider Instance Password";
typeName = "STRING"; typeName = "STRING";
defaultValue = "password"; defaultValue = "password";
}; };
class ModuleDescription: ModuleDescription {};
};
class ModuleDescription: ModuleDescription {
description = "Generate the initial ARMATAK configuration, syncronizing all players to the TAK server instance";
sync[] = {"LocationArea_F"};
class LocationArea_F {
description[] = {
"First line",
"Second line"
};
position = 1;
direction = 1;
optional = 1;
duplicate = 1;
synced[] = { "BluforUnit", "AnyBrain" };
};
class BluforUnit
{
description = "Short description";
displayName = "Any BLUFOR unit";
icon = "iconMan";
side = 1;
};
}; };
}; };
}; };

View File

@@ -7,15 +7,15 @@ params [
]; ];
if (isServer) exitWith { if (isServer) exitWith {
private _instance_address = GETVAR(_logic,instance_address,false); private _instance_address = GETVAR(_logic,GVAR(instanceAddress),false);
private _instance_port = GETVAR(_logic,instance_port,false); private _instance_port = GETVAR(_logic,GVAR(instancePort),false);
private _instance_auth_user = GETVAR(_logic,instance_auth_user,false); private _instance_auth_user = GETVAR(_logic,GVAR(instanceAuthUser),false);
private _instance_auth_pass = GETVAR(_logic,instance_auth_pass,false); private _instance_auth_pass = GETVAR(_logic,GVAR(instanceAuthPassword),false);
SETMVAR(GVAR(instance_address),_instance_address); SETMVAR(GVAR(instanceAddress),_instance_address);
SETMVAR(GVAR(instance_port),_instance_port); SETMVAR(GVAR(instancePort),_instance_port);
SETMVAR(GVAR(instance_auth_user),_instance_auth_user); SETMVAR(GVAR(instanceAuthUser),_instance_auth_user);
SETMVAR(GVAR(instance_auth_pass),_instance_auth_pass); SETMVAR(GVAR(instanceAuthPassword),_instance_auth_pass);
_startAction = [ _startAction = [
QGVAR(startStream), QGVAR(startStream),

View File

@@ -4,6 +4,7 @@ use std::process::Command;
use std::sync::mpsc::{self, Receiver, Sender}; use std::sync::mpsc::{self, Receiver, Sender};
use std::sync::Mutex; use std::sync::Mutex;
use std::thread; use std::thread;
use std::time::Duration;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
use std::os::windows::process::CommandExt; use std::os::windows::process::CommandExt;
@@ -29,11 +30,14 @@ pub fn start_stream(
"VIDEO ERROR", "VIDEO ERROR",
"Screen capture is only supported on Windows", "Screen capture is only supported on Windows",
); );
return "screen capture unsupported";
} }
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
{ {
let (tx, rx): (Sender<()>, Receiver<()>) = mpsc::channel(); let (stop_tx, stop_rx): (Sender<()>, Receiver<()>) = mpsc::channel();
let (status_tx, status_rx): (Sender<Result<(), String>>, Receiver<Result<(), String>>) = mpsc::channel();
let rtsp_url = if username.is_empty() || password.is_empty() { let rtsp_url = if username.is_empty() || password.is_empty() {
format!("rtsp://{}:{}/{}", address, port, stream_path) format!("rtsp://{}:{}/{}", address, port, stream_path)
} else { } else {
@@ -47,7 +51,6 @@ pub fn start_stream(
thread::spawn(move || { thread::spawn(move || {
let mut cmd = Command::new("ffmpeg"); let mut cmd = Command::new("ffmpeg");
cmd.args(&[ cmd.args(&[
"-f", "-f",
"gdigrab", "gdigrab",
@@ -60,30 +63,57 @@ pub fn start_stream(
&rtsp_url_clone, &rtsp_url_clone,
]); ]);
let mut child = cmd.creation_flags(CREATE_NO_WINDOW).spawn().unwrap(); // Try to spawn ffmpeg process
let child_result = cmd.creation_flags(CREATE_NO_WINDOW).spawn();
if rx.recv().is_err() { match child_result {
let _ = ctx.callback_null("VIDEO ERROR", "Error receiving stop signal"); Ok(mut child) => {
} let _ = status_tx.send(Ok(()));
if stop_rx.recv().is_err() {}
if let Err(e) = child.kill() { let _ = child.kill();
let _ = ctx.callback_data( }
"VIDEO ERROR", Err(e) => {
"Failed to Stop FFmpeg", let _ = status_tx.send(Err(format!("Failed to start FFmpeg: {}", e)));
e.to_string(), // Return early, nothing else to do
); }
} }
}); });
// Save the stop channel so we can stop later
match STREAM_CTRL.lock() { match STREAM_CTRL.lock() {
Ok(mut lock) => *lock = Some(tx), Ok(mut lock) => *lock = Some(stop_tx),
Err(e) => { Err(e) => {
eprintln!("Failed to acquire lock: {}", e); let _ = ctx.callback_data(
"VIDEO ERROR",
"Failed to acquire lock for stream control",
e.to_string(),
);
return "stream control lock error";
}
}
// Wait up to 2 seconds to see if ffmpeg started correctly
match status_rx.recv_timeout(Duration::from_secs(2)) {
Ok(Ok(())) => {
let _ = ctx.callback_null("VIDEO", "FFmpeg started successfully");
"starting video stream"
}
Ok(Err(e)) => {
let _ = ctx.callback_data("VIDEO ERROR", "FFmpeg failed to start", e);
"ffmpeg failed to start"
}
Err(_) => {
let _ = ctx.callback_null("VIDEO ERROR", "FFmpeg did not respond in time");
"ffmpeg did not respond"
} }
} }
} }
"starting video stream" #[cfg(not(any(target_os = "windows", target_os = "linux")))]
{
ctx.callback_null("VIDEO ERROR", "Screen capture is only supported on Windows");
"unsupported platform"
}
} }
pub fn stop_stream(ctx: Context) -> &'static str { pub fn stop_stream(ctx: Context) -> &'static str {
@@ -96,20 +126,23 @@ pub fn stop_stream(ctx: Context) -> &'static str {
"Failed to send stop signal", "Failed to send stop signal",
e.to_string(), e.to_string(),
); );
"error sending stop"
} else {
let _ = ctx.callback_null("VIDEO", "Sent stop signal to FFmpeg");
"stopping video stream"
} }
} else { } else {
let _ = let _ = ctx.callback_null("VIDEO ERROR", "Tried to stop a nonexistent stream");
ctx.callback_null("VIDEO ERROR", "Tried to stop a nonexistent stream"); "no stream to stop"
} }
} }
Err(e) => { Err(e) => {
let _ = ctx.callback_data( let _ = ctx.callback_data(
"VIDEO ERROR", "VIDEO ERROR",
"Failed to acquire lock", "Failed to acquire lock for stop",
e.to_string(), e.to_string(),
); );
"lock error"
} }
} }
}
"stopping video stream"
}