mirror of
https://github.com/valmojr/armatak.git
synced 2026-06-13 15:23:28 +00:00
refactored video streaming module
This commit is contained in:
@@ -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;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -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),
|
||||||
|
|||||||
@@ -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"
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user