mirror of
https://github.com/valmojr/armatak.git
synced 2026-06-13 13:23:28 +00:00
refactored video streaming module
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
class CfgVehicles {
|
||||
class Logic;
|
||||
class Logic;
|
||||
|
||||
class Module_F : Logic
|
||||
{
|
||||
class AttributesBase
|
||||
@@ -18,84 +19,54 @@ class CfgVehicles {
|
||||
class AnyBrain;
|
||||
};
|
||||
};
|
||||
class armatak_module_core;
|
||||
class armatak_module_video_stream_core: armatak_module_core {
|
||||
class EGVAR(server,moduleBase);
|
||||
class GVAR(videoModule): EGVAR(server,moduleBase) {
|
||||
scope = 2;
|
||||
displayname = "ARMATAK MediaMTX Video Feed Parser";
|
||||
icon = "\a3\Modules_F_Curator\Data\iconcuratorsetcamera_ca.paa";
|
||||
category = "armatak_module_category";
|
||||
function = "armatak_fnc_video_init";
|
||||
scopeCurator = 0;
|
||||
displayname = "Video Streaming Handler";
|
||||
icon = "\a3\Modules_F_Curator\Data\iconRadio_ca.paa";
|
||||
category = QEGVAR(main,moduleCategory);
|
||||
function = QFUNC(videoParser);
|
||||
functionPriority = 1;
|
||||
isGlobal = 0;
|
||||
isTriggerActivated = 0;
|
||||
isTriggerActivated = 1;
|
||||
isDisposable = 1;
|
||||
is3den = 0;
|
||||
curatorCanAttach = 0;
|
||||
curatorInfoType = "RscDisplayAttributeModuleNuke";
|
||||
|
||||
canSetArea = 0;
|
||||
canSetAreaShape = 0;
|
||||
canSetAreaHeight = 0;
|
||||
|
||||
class AttributesValues {
|
||||
size3[] = { 1, 1, -1 };
|
||||
isRectangle = 0;
|
||||
};
|
||||
|
||||
class Attributes: AttributesBase {
|
||||
class armatak_module_mediamtx_video_stream_instance_address: Edit {
|
||||
property = "armatak_module_mediamtx_video_stream_instance_address";
|
||||
class GVAR(instanceAddress): Edit {
|
||||
property = QGVAR(instanceAddress);
|
||||
displayname = "MediaMTX Provider Address";
|
||||
tooltip = "MediaMTX Provider Instance Address";
|
||||
typeName = "STRING";
|
||||
defaultValue = "localhost";
|
||||
};
|
||||
class armatak_module_mediamtx_video_stream_instance_port: Edit {
|
||||
class GVAR(instancePort): Edit {
|
||||
property = "armatak_module_mediamtx_video_stream_instance_port";
|
||||
displayname = "MediaMTX Provider Port";
|
||||
tooltip = "MediaMTX Provider Port for handling video streams";
|
||||
typeName = "STRING";
|
||||
defaultValue = "8554";
|
||||
};
|
||||
class armatak_module_mediamtx_video_stream_instance_auth_user: Edit {
|
||||
property = "armatak_module_mediamtx_video_stream_instance_auth_user";
|
||||
class GVAR(instanceAuthUser): Edit {
|
||||
property = QGVAR(instanceAuthUser);
|
||||
displayname = "MediaMTX Provider Username";
|
||||
tooltip = "MediaMTX Provider Instance Username";
|
||||
typeName = "STRING";
|
||||
defaultValue = "administrator";
|
||||
};
|
||||
class armatak_module_mediamtx_video_stream_instance_auth_pass: Edit {
|
||||
property = "armatak_module_mediamtx_video_stream_instance_auth_pass";
|
||||
class GVAR(instanceAuthPassword): Edit {
|
||||
property = QGVAR(instanceAuthPassword);
|
||||
displayname = "MediaMTX Provider Password";
|
||||
tooltip = "MediaMTX Provider Instance Password";
|
||||
typeName = "STRING";
|
||||
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;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -7,15 +7,15 @@ params [
|
||||
];
|
||||
|
||||
if (isServer) exitWith {
|
||||
private _instance_address = GETVAR(_logic,instance_address,false);
|
||||
private _instance_port = GETVAR(_logic,instance_port,false);
|
||||
private _instance_auth_user = GETVAR(_logic,instance_auth_user,false);
|
||||
private _instance_auth_pass = GETVAR(_logic,instance_auth_pass,false);
|
||||
private _instance_address = GETVAR(_logic,GVAR(instanceAddress),false);
|
||||
private _instance_port = GETVAR(_logic,GVAR(instancePort),false);
|
||||
private _instance_auth_user = GETVAR(_logic,GVAR(instanceAuthUser),false);
|
||||
private _instance_auth_pass = GETVAR(_logic,GVAR(instanceAuthPassword),false);
|
||||
|
||||
SETMVAR(GVAR(instance_address),_instance_address);
|
||||
SETMVAR(GVAR(instance_port),_instance_port);
|
||||
SETMVAR(GVAR(instance_auth_user),_instance_auth_user);
|
||||
SETMVAR(GVAR(instance_auth_pass),_instance_auth_pass);
|
||||
SETMVAR(GVAR(instanceAddress),_instance_address);
|
||||
SETMVAR(GVAR(instancePort),_instance_port);
|
||||
SETMVAR(GVAR(instanceAuthUser),_instance_auth_user);
|
||||
SETMVAR(GVAR(instanceAuthPassword),_instance_auth_pass);
|
||||
|
||||
_startAction = [
|
||||
QGVAR(startStream),
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::process::Command;
|
||||
use std::sync::mpsc::{self, Receiver, Sender};
|
||||
use std::sync::Mutex;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
use std::os::windows::process::CommandExt;
|
||||
@@ -29,11 +30,14 @@ pub fn start_stream(
|
||||
"VIDEO ERROR",
|
||||
"Screen capture is only supported on Windows",
|
||||
);
|
||||
return "screen capture unsupported";
|
||||
}
|
||||
|
||||
#[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() {
|
||||
format!("rtsp://{}:{}/{}", address, port, stream_path)
|
||||
} else {
|
||||
@@ -47,7 +51,6 @@ pub fn start_stream(
|
||||
|
||||
thread::spawn(move || {
|
||||
let mut cmd = Command::new("ffmpeg");
|
||||
|
||||
cmd.args(&[
|
||||
"-f",
|
||||
"gdigrab",
|
||||
@@ -60,30 +63,57 @@ pub fn start_stream(
|
||||
&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() {
|
||||
let _ = ctx.callback_null("VIDEO ERROR", "Error receiving stop signal");
|
||||
}
|
||||
|
||||
if let Err(e) = child.kill() {
|
||||
let _ = ctx.callback_data(
|
||||
"VIDEO ERROR",
|
||||
"Failed to Stop FFmpeg",
|
||||
e.to_string(),
|
||||
);
|
||||
match child_result {
|
||||
Ok(mut child) => {
|
||||
let _ = status_tx.send(Ok(()));
|
||||
if stop_rx.recv().is_err() {}
|
||||
let _ = child.kill();
|
||||
}
|
||||
Err(e) => {
|
||||
let _ = status_tx.send(Err(format!("Failed to start FFmpeg: {}", e)));
|
||||
// Return early, nothing else to do
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Save the stop channel so we can stop later
|
||||
match STREAM_CTRL.lock() {
|
||||
Ok(mut lock) => *lock = Some(tx),
|
||||
Ok(mut lock) => *lock = Some(stop_tx),
|
||||
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 {
|
||||
@@ -96,20 +126,23 @@ pub fn stop_stream(ctx: Context) -> &'static str {
|
||||
"Failed to send stop signal",
|
||||
e.to_string(),
|
||||
);
|
||||
"error sending stop"
|
||||
} else {
|
||||
let _ = ctx.callback_null("VIDEO", "Sent stop signal to FFmpeg");
|
||||
"stopping video stream"
|
||||
}
|
||||
} else {
|
||||
let _ =
|
||||
ctx.callback_null("VIDEO ERROR", "Tried to stop a nonexistent stream");
|
||||
let _ = ctx.callback_null("VIDEO ERROR", "Tried to stop a nonexistent stream");
|
||||
"no stream to stop"
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
let _ = ctx.callback_data(
|
||||
"VIDEO ERROR",
|
||||
"Failed to acquire lock",
|
||||
"Failed to acquire lock for stop",
|
||||
e.to_string(),
|
||||
);
|
||||
"lock error"
|
||||
}
|
||||
}
|
||||
|
||||
"stopping video stream"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user