initial commit
This commit is contained in:
13
README.md
Normal file
13
README.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
I need one more component here, the service manager service site
|
||||||
|
I'll use what I built for the carputer, and add to it
|
||||||
|
Things to add:
|
||||||
|
- Duration selection grid
|
||||||
|
- Duration Submission Selection API
|
||||||
|
- Cron job + helper script
|
||||||
|
|
||||||
|
I will update the API to maintain the selection state and return it from the API
|
||||||
|
I will write a selection grid that updates color depending on selction
|
||||||
|
It will also be a styled radio button states 0-3 for 6ht, 2hr, 1hr, and 30min
|
||||||
|
I will write a helper cron script that runs every second that will stop the service when the API says so
|
||||||
|
The API will have a function that will return the expected state of the service
|
||||||
|
When the helper script sees it not match, it will stop the service
|
||||||
14
archive/docker-compose-owncast.yaml.j2
Normal file
14
archive/docker-compose-owncast.yaml.j2
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
services:
|
||||||
|
|
||||||
|
owncast:
|
||||||
|
container_name: owncast
|
||||||
|
image: owncast/owncast:latest
|
||||||
|
volumes:
|
||||||
|
- {{ owncast_working_folder }}/data:/app/data
|
||||||
|
- {{ owncast_capture_folder }}:/app/capture
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
||||||
|
- 1935:1935
|
||||||
|
restart: always
|
||||||
|
network_mode: bridge
|
||||||
|
|
||||||
29
archive/owncast.yaml
Normal file
29
archive/owncast.yaml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
###############################################
|
||||||
|
# Start owncast
|
||||||
|
###############################################
|
||||||
|
- name: start owncast
|
||||||
|
block:
|
||||||
|
|
||||||
|
# Create service Folder
|
||||||
|
- name: video_capture - owncast - create owncast_working_folder folder
|
||||||
|
file:
|
||||||
|
path: "{{ owncast_working_folder }}"
|
||||||
|
state: directory
|
||||||
|
mode: '0755'
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
|
||||||
|
# restore data later once i have a working setup
|
||||||
|
|
||||||
|
- name: video_capture - owncast - template config
|
||||||
|
template:
|
||||||
|
src: docker-compose-owncast.yaml.j2
|
||||||
|
dest: "{{ owncast_working_folder }}/docker-compose.yaml"
|
||||||
|
mode: 0644
|
||||||
|
|
||||||
|
- name: "video_capture - owncast - Start owncast container"
|
||||||
|
shell: "docker-compose -f {{ owncast_working_folder }}/docker-compose.yaml up -d"
|
||||||
|
register: docker_output
|
||||||
|
- debug: |
|
||||||
|
msg="{{ docker_output.stdout_lines }}"
|
||||||
|
msg="{{ docker_output.stderr_lines }}"
|
||||||
63
defaults/main.yaml
Normal file
63
defaults/main.yaml
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
working_storage: "/media/sd_card"
|
||||||
|
|
||||||
|
owncast_capture_folder: "{{ working_storage }}/owncast"
|
||||||
|
|
||||||
|
recording_capture_folder: "{{ working_storage }}/recordings"
|
||||||
|
|
||||||
|
streaming_working_folder: "/opt/cosmos/streamer"
|
||||||
|
|
||||||
|
mediamtx_working_folder: "/opt/cosmos/mediamtx"
|
||||||
|
|
||||||
|
service_control_folder: "/opt/cosmos/streamer/service_control"
|
||||||
|
|
||||||
|
video_device: "/dev/video0"
|
||||||
|
|
||||||
|
audio_device: "hw:0,0"
|
||||||
|
|
||||||
|
mediamtx_version: "amd64"
|
||||||
|
|
||||||
|
lsusb_device_ID: "534d:0021"
|
||||||
|
|
||||||
|
capture_device_ID_string: "MS210"
|
||||||
|
|
||||||
|
service_control_name: "stream_service.service"
|
||||||
|
|
||||||
|
service_name: "VCR Streamer"
|
||||||
|
|
||||||
|
container_name: "service_control"
|
||||||
|
|
||||||
|
mount_sd: true
|
||||||
|
|
||||||
|
format_sd: false
|
||||||
|
|
||||||
|
sd_unmounted: true
|
||||||
|
|
||||||
|
arm_arch: false
|
||||||
|
|
||||||
|
streamer_packages:
|
||||||
|
- ffmpeg
|
||||||
|
- alsa-utils
|
||||||
|
- alsa-oss
|
||||||
|
- alsa-firmware-loaders
|
||||||
|
- apulse
|
||||||
|
- libasound2-plugins
|
||||||
|
- v4l-utils
|
||||||
|
- usbutils
|
||||||
|
- python3-venv
|
||||||
|
|
||||||
|
# media_mtx variables
|
||||||
|
mediamtx_configs:
|
||||||
|
- search_string: 'record:'
|
||||||
|
line: ' record: yes'
|
||||||
|
- search_string: 'recordPath'
|
||||||
|
line: ' recordPath: {{ recording_capture_folder }}/%path/%Y-%m-%d_%H-%M/%H-%M-%S-%f'
|
||||||
|
- search_string: 'recordSegmentDuration'
|
||||||
|
line: ' recordSegmentDuration: 3600s'
|
||||||
|
- search_string: 'recordDeleteAfter'
|
||||||
|
line: ' recordDeleteAfter: 0s'
|
||||||
|
- search_string: 'recordMaxPartSize'
|
||||||
|
line: ' recordMaxPartSize: 500M'
|
||||||
|
|
||||||
|
...
|
||||||
233
files/service_control_api/website/index.php
Normal file
233
files/service_control_api/website/index.php
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
<?php
|
||||||
|
date_default_timezone_set('America/Los_Angeles');
|
||||||
|
# set default values
|
||||||
|
$status = "Unchecked";
|
||||||
|
$message = "No attempt made yet.";
|
||||||
|
$button_text = "Pending";
|
||||||
|
$button_active = false;
|
||||||
|
$button_recent = false;
|
||||||
|
$button_action = "";
|
||||||
|
$button_result = "";
|
||||||
|
$http_host = $_SERVER['HTTP_HOST'];
|
||||||
|
$debug_string = "";
|
||||||
|
|
||||||
|
if (isset($_GET['action'])) {
|
||||||
|
$debug_string = $debug_string."GET called, ".$_GET['action']."<br>";
|
||||||
|
$button_recent = true;
|
||||||
|
$button_result = runAPI($_GET['action']);
|
||||||
|
$debug_string = $debug_string."Button Result: ".$button_result."<br>";
|
||||||
|
sleep(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function runAPI($submitted_status) {
|
||||||
|
if(!isset($debug_string)){
|
||||||
|
$debug_string = "";
|
||||||
|
}
|
||||||
|
$debug_string = $debug_string."runAPI called, ".$submitted_status."<br>";
|
||||||
|
switch ($submitted_status) {
|
||||||
|
case "stop":
|
||||||
|
$apiUrl = "http://172.17.0.1:5000/stop";
|
||||||
|
break;
|
||||||
|
case "start":
|
||||||
|
$apiUrl = "http://172.17.0.1:5000/start";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$apiUrl = "http://172.17.0.1:5000/status";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// API URL
|
||||||
|
$debug_string = $debug_string."After switch, apiUrl is ".$apiUrl."<br>";
|
||||||
|
// Use file_get_contents or cURL to fetch the API data
|
||||||
|
try {
|
||||||
|
$response = file_get_contents($apiUrl);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$response = '{ "Message": "unknowable", "Status": "unknowable" }';
|
||||||
|
}
|
||||||
|
if ($response === FALSE) {
|
||||||
|
$debug_string = $debug_string."Response ERROR!!<br>";
|
||||||
|
return "error"; // API error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the JSON response (assuming the API returns JSON)
|
||||||
|
$data = json_decode($response, true);
|
||||||
|
$debug_string = $debug_string."Data from API call: ".$data['Message']."<br>";
|
||||||
|
// Assuming the API returns a single word result (like "success", "failure", etc.)
|
||||||
|
return isset($data['Status']) ? $data['Status'] : 'unknown';
|
||||||
|
}
|
||||||
|
|
||||||
|
// | awk '{printf(\"%.5f\n\", $1)}'
|
||||||
|
// | awk '{printf(\"%.5f\n\", $1)}'
|
||||||
|
|
||||||
|
|
||||||
|
function getGPS(){
|
||||||
|
// check the API
|
||||||
|
$gps_data = file_get_contents("http://172.17.0.1:5000/return_gps");
|
||||||
|
try {
|
||||||
|
$gps_data = json_decode($gps_data, true);
|
||||||
|
|
||||||
|
} catch (Exception $e){
|
||||||
|
$gps_data = '{c}';
|
||||||
|
}
|
||||||
|
|
||||||
|
//set the vars
|
||||||
|
$LAT = $gps_data['lat'];
|
||||||
|
$LON = $gps_data['lon'];
|
||||||
|
$SPEED = $gps_data['speed'];
|
||||||
|
|
||||||
|
if (is_null($LAT) || $LAT == 0) {
|
||||||
|
return "No GPS data available - null LAT";
|
||||||
|
}
|
||||||
|
if (is_null($LON) || $LON == 0) {
|
||||||
|
return "No GPS data available - null LON";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $LAT.", ".$LON.", ".$SPEED."mph";
|
||||||
|
}
|
||||||
|
|
||||||
|
// URL of the external API
|
||||||
|
$apiUrl = "http://172.17.0.1:5000/status";
|
||||||
|
|
||||||
|
// Use file_get_contents to fetch data from the API
|
||||||
|
try {
|
||||||
|
$response = file_get_contents($apiUrl);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$response = '{ "Message": "unknowable", "Status": "unknowable" }';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the request was successful
|
||||||
|
if ($response === FALSE) {
|
||||||
|
$data = "Failed to fetch data.";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Decode the JSON response (assuming the API returns JSON)
|
||||||
|
$data = json_decode($response, true);
|
||||||
|
// If you want to display specific data, adjust this part
|
||||||
|
// For example, if the API returns a 'message' key, display it
|
||||||
|
if (isset($data['Message'])) {
|
||||||
|
$message = $data['Message'];
|
||||||
|
} else {
|
||||||
|
$message = "No message found in the response.";
|
||||||
|
}
|
||||||
|
if (isset($data['Status'])) {
|
||||||
|
$status = $data['Status'];
|
||||||
|
} else {
|
||||||
|
$status = "None";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($status) {
|
||||||
|
case "active":
|
||||||
|
$button_text = "Stop Service";
|
||||||
|
$button_active = true;
|
||||||
|
break;
|
||||||
|
case "inactive":
|
||||||
|
$button_text = "Start Service";
|
||||||
|
$button_active = true;
|
||||||
|
break;
|
||||||
|
case "failed":
|
||||||
|
$button_text = "Start Service - Warning";
|
||||||
|
$button_active = true;
|
||||||
|
break;
|
||||||
|
case "deactivating":
|
||||||
|
$button_text = "Service Stopping...";
|
||||||
|
$button_active = false;
|
||||||
|
break;
|
||||||
|
case "unknowable":
|
||||||
|
$button_text = "Service Manager Down...";
|
||||||
|
$button_active = false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$button_text = "Error, No Status";
|
||||||
|
$button_active = false;
|
||||||
|
$status="unknown";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($button_active && !$button_recent){
|
||||||
|
$action = "";
|
||||||
|
if($status == "active"){
|
||||||
|
$action = "stop";
|
||||||
|
}
|
||||||
|
else if($status == "inactive"){
|
||||||
|
$action = "start";
|
||||||
|
}
|
||||||
|
else if($status == "failed"){
|
||||||
|
$action = "start";
|
||||||
|
}
|
||||||
|
$button_action = ' onclick="location.href=\'/?action='.$action.'\'" ';
|
||||||
|
}
|
||||||
|
|
||||||
|
if($button_recent){
|
||||||
|
/*
|
||||||
|
$delay = 5; // Number of seconds before redirection
|
||||||
|
echo "You will be redirected in $delay seconds.";
|
||||||
|
header("Refresh: $delay; url=https://example.com");
|
||||||
|
exit();
|
||||||
|
*/
|
||||||
|
header("Location: http://".$http_host);
|
||||||
|
exit(); // Always include exit() after headers are sent
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>API Data Display</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<div class="button-container">
|
||||||
|
<button <?php echo $button_action; ?> class="<?php echo $status; ?>" id="phpButton"<?php if(!$button_active || $button_recent) {echo " disabled";} ?>>
|
||||||
|
<?php echo $button_text; ?>
|
||||||
|
</button>
|
||||||
|
<p>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
<div class="api-data">
|
||||||
|
<!-- PHP will inject the data here -->
|
||||||
|
<?php
|
||||||
|
echo "Full Message:<br>".htmlspecialchars($message)."<p>";
|
||||||
|
echo "Current Date:<br>".date("F j, Y, g:i:s a")."<p>";
|
||||||
|
echo "Current GPS Data:<br>".getGPS();
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
// When the button is clicked, send an AJAX request to fetch new data
|
||||||
|
document.getElementById('phpButton').addEventListener('click', function() {
|
||||||
|
// Disable button to avoid multiple clicks
|
||||||
|
this.disabled = true;
|
||||||
|
this.innerText = 'Loading...';
|
||||||
|
|
||||||
|
// Make an AJAX request
|
||||||
|
fetch('index.php', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
|
'status': '<?php echo $status; ?>'
|
||||||
|
},
|
||||||
|
body: 'action=fetch_data' // Trigger the PHP function via POST
|
||||||
|
})
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Automatically refresh the page every second
|
||||||
|
setTimeout(function() {
|
||||||
|
location.reload();
|
||||||
|
}, <?php if($button_recent){ echo "5000"; } else{ echo "1000"; } ?>); // Refresh interval (1000 ms = 1 second)
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
||||||
86
files/service_control_api/website/styles.css
Normal file
86
files/service_control_api/website/styles.css
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: #2c3e50; /* Dark background color */
|
||||||
|
color: #bdc3c7; /* Dimmer text color */
|
||||||
|
zoom: 1.5;
|
||||||
|
overflow: hidden; /* Hide scrollbars */
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 500px;
|
||||||
|
margin: 0 auto;
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #34495e; /* Darker background for container */
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); /* Slightly darker shadow */
|
||||||
|
}
|
||||||
|
.api-data {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
color: #bdc3c7; /* Dimmer text color */
|
||||||
|
}
|
||||||
|
.loading {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
background-color: #34495e; /* Darker background for container-wide */
|
||||||
|
}
|
||||||
|
.status-button {
|
||||||
|
padding: 10px 20px;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 20px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
.button-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center; /* This centers the button horizontally */
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
background: radial-gradient(circle, #ff7e5f 0%, #feb47b 100%);
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
font-size: 30px;
|
||||||
|
padding: 25px 50px;
|
||||||
|
text-align: center;
|
||||||
|
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: 30px;
|
||||||
|
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23);
|
||||||
|
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22);
|
||||||
|
}
|
||||||
|
.inactive {
|
||||||
|
background-color: #0b660e;
|
||||||
|
background: radial-gradient(circle, #0b660e 0%, #2b922f 100%);
|
||||||
|
color: #bdc3c7; /* Dimmer text color */
|
||||||
|
}
|
||||||
|
.active {
|
||||||
|
background-color: #a5150b;
|
||||||
|
background: radial-gradient(circle, #a5150b 0%, #d6382d 100%);
|
||||||
|
color: #bdc3c7; /* Dimmer text color */
|
||||||
|
}
|
||||||
|
.failed {
|
||||||
|
background-color: #671281;
|
||||||
|
background: radial-gradient(circle, #671281 0%, #8423a1 100%);
|
||||||
|
color: #bdc3c7; /* Dimmer text color */
|
||||||
|
}
|
||||||
|
.deactivating {
|
||||||
|
background-color: #003699;
|
||||||
|
background: radial-gradient(circle, #003699 0%, #337aff 100%);
|
||||||
|
color: #bdc3c7; /* Dimmer text color */
|
||||||
|
}
|
||||||
|
.deactivating {
|
||||||
|
background-color: #0d2b2c;
|
||||||
|
background: radial-gradient(circle, #0d2b2c 0%, #123738 100%);
|
||||||
|
color: #bdc3c7; /* Dimmer text color */
|
||||||
|
}
|
||||||
|
.unknown {
|
||||||
|
background-color: #ec701e;
|
||||||
|
background: radial-gradient(circle, #c9580d 0%, #ec701e 100%);
|
||||||
|
color: #bdc3c7; /* Dimmer text color */
|
||||||
|
}
|
||||||
42
tasks/main.yaml
Normal file
42
tasks/main.yaml
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
# - name: Video Capture - Configure Owncast
|
||||||
|
# include_tasks: owncast.yaml
|
||||||
|
|
||||||
|
- name: Video Capture - Check arch if needed
|
||||||
|
when: refresh_special | bool
|
||||||
|
block:
|
||||||
|
|
||||||
|
- name: Video Capture - Check CPU Arch
|
||||||
|
shell: "dpkg --print-architecture"
|
||||||
|
register: cpu_architecture_output
|
||||||
|
|
||||||
|
- name: Video Capture - Set cpu_architecture variable
|
||||||
|
set_fact:
|
||||||
|
cpu_architecture: "{{ cpu_architecture_output.stdout_lines[0] }}"
|
||||||
|
|
||||||
|
- name: Video Capture - Install Packages
|
||||||
|
when: not refresh_special | bool
|
||||||
|
apt:
|
||||||
|
name:
|
||||||
|
- "{{ streamer_packages_items }}"
|
||||||
|
state: present
|
||||||
|
loop: "{{ streamer_packages }}"
|
||||||
|
loop_control:
|
||||||
|
loop_var: streamer_packages_items
|
||||||
|
|
||||||
|
- name: Video Capture - SD Card Handler
|
||||||
|
when: mount_sd | bool
|
||||||
|
include_tasks: sd_handler.yaml
|
||||||
|
|
||||||
|
- name: Video Capture - Configure MediaMTX
|
||||||
|
include_tasks: mediamtx.yaml
|
||||||
|
|
||||||
|
- name: Video Capture - Configure Streaming
|
||||||
|
include_tasks: streamer.yaml
|
||||||
|
|
||||||
|
#- name: Video Capture - Configure service control
|
||||||
|
# include_tasks: service_control.yaml
|
||||||
|
|
||||||
|
|
||||||
|
...
|
||||||
82
tasks/mediamtx.yaml
Normal file
82
tasks/mediamtx.yaml
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
# mediamtx might automatically make video files
|
||||||
|
|
||||||
|
# newest release URL:
|
||||||
|
# curl -sL https://api.github.com/repos/bluenviron/mediamtx/releases/latest | \
|
||||||
|
# grep browser_download_url | grep linux_amd64 | cut -d\" -f 4
|
||||||
|
|
||||||
|
- name: MediaMTX - stop mediamtx_service if running
|
||||||
|
systemd:
|
||||||
|
name: mediamtx_service.service
|
||||||
|
state: stopped
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
# Create service Folders
|
||||||
|
- name: MediaMTX - create mediamtx_working_folder folder
|
||||||
|
file:
|
||||||
|
path: "{{ mediamtx_working_folder }}"
|
||||||
|
state: directory
|
||||||
|
mode: '0755'
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
|
||||||
|
- name: MediaMTX - check for arm
|
||||||
|
when: '"arm" in cpu_architecture'
|
||||||
|
set_fact:
|
||||||
|
mediamtx_version: "arm64"
|
||||||
|
|
||||||
|
- name: MediaMTX - get current release URL
|
||||||
|
shell: |
|
||||||
|
curl -sL https://api.github.com/repos/bluenviron/mediamtx/releases/latest | \
|
||||||
|
grep browser_download_url | grep linux_{{ mediamtx_version }} | cut -d\" -f 4
|
||||||
|
register: mediamtx_latest_url
|
||||||
|
- debug:
|
||||||
|
msg: "URL To Download: {{ mediamtx_latest_url.stdout_lines[0] }}"
|
||||||
|
|
||||||
|
- name: MediaMTX - get current release archive
|
||||||
|
shell: "curl -s -o {{ mediamtx_working_folder }}/mediamtx.tar.gz -L {{ mediamtx_latest_url.stdout_lines[0] }}"
|
||||||
|
|
||||||
|
- name: MediaMTX - extract archive
|
||||||
|
unarchive:
|
||||||
|
# src: "/var/jenkins_home/ansible-files/programs/mediamtx_v1.14.0_linux_amd64.tar.gz"
|
||||||
|
src: "{{ mediamtx_working_folder }}/mediamtx.tar.gz"
|
||||||
|
dest: "{{ mediamtx_working_folder }}"
|
||||||
|
mode: '0755'
|
||||||
|
remote_src: yes
|
||||||
|
|
||||||
|
- name: MediaMTX - update configs
|
||||||
|
lineinfile:
|
||||||
|
path: "{{ mediamtx_working_folder }}/mediamtx.yml"
|
||||||
|
search_string: "{{ mediamtx_configs_item.search_string }}"
|
||||||
|
line: "{{ mediamtx_configs_item.line }}"
|
||||||
|
loop: "{{ mediamtx_configs }}"
|
||||||
|
loop_control:
|
||||||
|
loop_var: mediamtx_configs_item
|
||||||
|
|
||||||
|
# - name: MediaMTX - config - enable recording
|
||||||
|
# lineinfile:
|
||||||
|
# path: "{{ mediamtx_working_folder }}/mediamtx.yml"
|
||||||
|
# search_string: 'record'
|
||||||
|
# line: ' record: yes'
|
||||||
|
#
|
||||||
|
- name: MediaMTX - create service file
|
||||||
|
template:
|
||||||
|
src: mediamtx_service.service.j2
|
||||||
|
dest: "/etc/systemd/system/mediamtx_service.service"
|
||||||
|
mode: 0644
|
||||||
|
|
||||||
|
# daemon reload
|
||||||
|
- name: MediaMTX - daemon reload
|
||||||
|
systemd:
|
||||||
|
daemon_reload: yes
|
||||||
|
|
||||||
|
# Enable and start
|
||||||
|
- name: MediaMTX - enable and start mediamtx_service
|
||||||
|
systemd:
|
||||||
|
name: mediamtx_service.service
|
||||||
|
state: started
|
||||||
|
enabled: yes
|
||||||
|
|
||||||
|
|
||||||
|
...
|
||||||
102
tasks/sd_handler.yaml
Normal file
102
tasks/sd_handler.yaml
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
# when ran, this will look for an SD card
|
||||||
|
# optionally format it
|
||||||
|
# map it to working_storage
|
||||||
|
# can only run on arm64
|
||||||
|
# '"arm64" in cpu_architecture'
|
||||||
|
|
||||||
|
|
||||||
|
- name: Video Capture - Check for SD
|
||||||
|
shell: "df"
|
||||||
|
register: df_check_output
|
||||||
|
|
||||||
|
- name: Video Capture - Set sd_unmounted
|
||||||
|
when: working_storage in df_check_output.stdout
|
||||||
|
set_fact:
|
||||||
|
sd_unmounted: false
|
||||||
|
|
||||||
|
- name: Video Capture - Set arm_arch
|
||||||
|
when: '"arm64" in cpu_architecture'
|
||||||
|
set_fact:
|
||||||
|
arm_arch: true
|
||||||
|
|
||||||
|
- name: SD Handler - Checks have passed
|
||||||
|
when: sd_unmounted and arm_arch | bool
|
||||||
|
block:
|
||||||
|
|
||||||
|
- name: SD Handler - format SD card
|
||||||
|
when: format_sd | bool
|
||||||
|
block:
|
||||||
|
|
||||||
|
- name: dummy task
|
||||||
|
debug:
|
||||||
|
msg: "nothing to see here, move along"
|
||||||
|
|
||||||
|
- name: SD Handler - find SD card
|
||||||
|
block:
|
||||||
|
|
||||||
|
- name: SD Handler - get boot device
|
||||||
|
shell: "blkid | grep '\"boot\"' | cut -d: -f 1 | cut -d/ -f 3 | cut -dp -f 1"
|
||||||
|
register: boot_device
|
||||||
|
|
||||||
|
- name: SD Handler - get sd card device
|
||||||
|
shell: "lsblk -o NAME,SIZE --nodeps | grep -v -e loop -e {{ boot_device.stdout_lines[0] }} -e NAME | cut -d ' ' -f 1"
|
||||||
|
register: sd_card_device
|
||||||
|
|
||||||
|
- name: SD Handler - get sd card uuid
|
||||||
|
shell: "blkid | grep {{ sd_card_device.stdout_lines[0] }} | awk '{for (i=1; i<=NF; i++) print $i}' | grep UUID | grep -v PART | cut -d '\"' -f 2"
|
||||||
|
register: sd_card_uuid_output
|
||||||
|
|
||||||
|
- name: SD Handler - set uuid variable
|
||||||
|
set_fact:
|
||||||
|
sd_card_uuid: "{{ sd_card_uuid_output.stdout_lines[0] }}"
|
||||||
|
|
||||||
|
- debug:
|
||||||
|
msg: "UUID Found: {{ sd_card_uuid }}"
|
||||||
|
|
||||||
|
- name: SD Handler - mount sd card
|
||||||
|
block:
|
||||||
|
|
||||||
|
- name: SD Handler - check folder
|
||||||
|
file:
|
||||||
|
path: "{{ working_storage }}"
|
||||||
|
state: directory
|
||||||
|
mode: '0755'
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
|
||||||
|
- name: SD Handler - Generate fstab entry
|
||||||
|
set_fact:
|
||||||
|
fstab_line_sd: "UUID={{ sd_card_uuid }} {{ working_storage }} ext4 errors=remount-ro 0 1"
|
||||||
|
|
||||||
|
- name: SD Handler - add fstab entry
|
||||||
|
lineinfile:
|
||||||
|
path: "/etc/fstab"
|
||||||
|
search_string: "{{ sd_card_uuid }}"
|
||||||
|
line: "{{ fstab_line_sd }}"
|
||||||
|
|
||||||
|
- debug:
|
||||||
|
msg: |
|
||||||
|
fstab entry:
|
||||||
|
{{ fstab_line_sd }}
|
||||||
|
|
||||||
|
- name: SD Handler - daemon reload
|
||||||
|
systemd:
|
||||||
|
daemon_reload: yes
|
||||||
|
|
||||||
|
- name: SD Handler - Mount it
|
||||||
|
shell: mount -a
|
||||||
|
|
||||||
|
- name: SD Handler - validate this
|
||||||
|
block:
|
||||||
|
|
||||||
|
- name: SD Handler - check for new mount point
|
||||||
|
shell: "df -h | grep -e Size -e {{ working_storage }}"
|
||||||
|
register: sd_test_output
|
||||||
|
|
||||||
|
- debug:
|
||||||
|
msg: "{{ sd_test_output.stdout_lines }}"
|
||||||
|
|
||||||
|
|
||||||
|
...
|
||||||
139
tasks/service_control.yaml
Normal file
139
tasks/service_control.yaml
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
# lifted directly from my carputer playbook
|
||||||
|
###############################################
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
# This part sets up python serice control api
|
||||||
|
###############################################
|
||||||
|
|
||||||
|
- name: service control api
|
||||||
|
block:
|
||||||
|
|
||||||
|
# Stop service
|
||||||
|
- name: video_capture - service_control api - stop service api
|
||||||
|
systemd:
|
||||||
|
name: service_control.service
|
||||||
|
state: stopped
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
# Create API Folder
|
||||||
|
- name: video_capture - service_control api - create api folder
|
||||||
|
file:
|
||||||
|
path: "{{ service_control_folder }}"
|
||||||
|
state: directory
|
||||||
|
mode: '0755'
|
||||||
|
|
||||||
|
# Copy API Code
|
||||||
|
- name: video_capture - service_control api - copy api code
|
||||||
|
template:
|
||||||
|
src: app-service.py.j2
|
||||||
|
dest: "{{ service_control_folder }}/app.py"
|
||||||
|
mode: 0644
|
||||||
|
|
||||||
|
# Create service_control api service
|
||||||
|
- name: video_capture - service_control api - create requirement file
|
||||||
|
copy:
|
||||||
|
dest: "{{ service_control_folder }}/requirements.txt"
|
||||||
|
content: |
|
||||||
|
Flask==2.1.0
|
||||||
|
pytz
|
||||||
|
requests
|
||||||
|
lxml
|
||||||
|
Werkzeug==2.0
|
||||||
|
mode: 0644
|
||||||
|
|
||||||
|
# build venv
|
||||||
|
- name: video_capture - service_control api - build venv
|
||||||
|
pip:
|
||||||
|
virtualenv: "{{ service_control_folder }}/venv"
|
||||||
|
requirements: "{{ service_control_folder }}/requirements.txt"
|
||||||
|
virtualenv_command: python3 -m venv
|
||||||
|
state: present
|
||||||
|
|
||||||
|
# Create service_control api service
|
||||||
|
- name: video_capture - service_control api - create service file
|
||||||
|
# vars:
|
||||||
|
template:
|
||||||
|
src: service_control.service.j2
|
||||||
|
dest: /etc/systemd/system/service_control.service
|
||||||
|
mode: 0644
|
||||||
|
|
||||||
|
# daemon reload
|
||||||
|
- name: video_capture - service_control api - daemon reload
|
||||||
|
systemd:
|
||||||
|
daemon_reload: yes
|
||||||
|
|
||||||
|
# Enable and start
|
||||||
|
- name: video_capture - service_control api - enable and start service api
|
||||||
|
systemd:
|
||||||
|
name: service_control.service
|
||||||
|
state: started
|
||||||
|
enabled: yes
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
# This part sets up serice control website
|
||||||
|
###############################################
|
||||||
|
|
||||||
|
- name: service control web interface
|
||||||
|
block:
|
||||||
|
|
||||||
|
- name: set docker folder variable
|
||||||
|
set_fact:
|
||||||
|
service_control_web_folder: "{{ service_control_folder }}/web"
|
||||||
|
|
||||||
|
# Create docker Folder
|
||||||
|
- name: service_control_website - create service_control_web_folder folder
|
||||||
|
file:
|
||||||
|
path: "{{ service_control_web_folder }}"
|
||||||
|
state: directory
|
||||||
|
mode: '0755'
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
|
||||||
|
- name: service_control_website - copy files for docker container
|
||||||
|
copy:
|
||||||
|
src: "service_control_api/website/"
|
||||||
|
dest: "{{ service_control_web_folder }}/html"
|
||||||
|
mode: 0755
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
|
||||||
|
# - name: service_control_website - template index.php
|
||||||
|
# template:
|
||||||
|
# src: index-service_control.php.j2
|
||||||
|
# dest: "{{ service_control_web_folder }}/html/index.php"
|
||||||
|
# mode: 0644
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
# Start service_control_website
|
||||||
|
###############################################
|
||||||
|
# https://unix.stackexchange.com/questions/265704/start-stop-a-systemd-service-at-specific-times
|
||||||
|
# i can create several conflicting services with various timeouts
|
||||||
|
|
||||||
|
- name: start service_control_website
|
||||||
|
block:
|
||||||
|
- name: set container variables
|
||||||
|
set_fact:
|
||||||
|
container_name: "service_control_website"
|
||||||
|
container_http_port: "8081"
|
||||||
|
|
||||||
|
- name: service_control_website - template config
|
||||||
|
template:
|
||||||
|
src: docker-compose-php.yaml.j2
|
||||||
|
dest: "{{ service_control_web_folder }}/docker-compose.yaml"
|
||||||
|
mode: 0644
|
||||||
|
|
||||||
|
- name: "service_control_website - Start container at 0.0.0.0:{{ container_http_port }}"
|
||||||
|
shell: "docker-compose -f {{ service_control_web_folder }}/docker-compose.yaml up -d"
|
||||||
|
register: docker_output
|
||||||
|
- debug: |
|
||||||
|
msg="{{ docker_output.stdout_lines }}"
|
||||||
|
msg="{{ docker_output.stderr_lines }}"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
...
|
||||||
72
tasks/streamer.yaml
Normal file
72
tasks/streamer.yaml
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
---
|
||||||
|
# video stream with ustreamer
|
||||||
|
# audio stream probably just a device
|
||||||
|
# looks like ffmpeg can do it
|
||||||
|
|
||||||
|
# Create service Folder
|
||||||
|
- name: video_capture - streamer - create streaming_working_folder folder
|
||||||
|
file:
|
||||||
|
path: "{{ streaming_working_folder }}"
|
||||||
|
state: directory
|
||||||
|
mode: '0755'
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
|
||||||
|
# this service shouldn't stay running
|
||||||
|
- name: video_capture - streamer - stop stream_service if running
|
||||||
|
systemd:
|
||||||
|
name: stream_service.service
|
||||||
|
state: stopped
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
# gonna try to automatically find the audio info
|
||||||
|
# the card is MS201
|
||||||
|
|
||||||
|
- name: video_capture - get card ID
|
||||||
|
shell: "arecord -l | grep {{ capture_device_ID_string }} | cut -d: -f1 | rev | cut -b 1"
|
||||||
|
register: sound_ID_0
|
||||||
|
|
||||||
|
- name: video_capture - get device ID
|
||||||
|
shell: "arecord -l | grep {{ capture_device_ID_string }} | cut -d: -f2 | rev | cut -b 1"
|
||||||
|
register: sound_ID_1
|
||||||
|
|
||||||
|
- name: video_capture - set audio_device
|
||||||
|
set_fact:
|
||||||
|
audio_device: "hw:{{ sound_ID_0.stdout_lines[0] }},{{ sound_ID_1.stdout_lines[0] }}"
|
||||||
|
|
||||||
|
# same with video, the lsusb ID is 534d:0021
|
||||||
|
|
||||||
|
- name: video_capture - get video device
|
||||||
|
shell: "v4l2-ctl --list-devices -z {{ lsusb_device_ID }}| grep video | head -n 1"
|
||||||
|
register: video_ID_0
|
||||||
|
|
||||||
|
- name: video_capture - set video_device
|
||||||
|
set_fact:
|
||||||
|
video_device: "{{ video_ID_0.stdout_lines[0] }}"
|
||||||
|
|
||||||
|
- name: video_capture - show results
|
||||||
|
debug:
|
||||||
|
msg: |
|
||||||
|
Audio Device: {{ audio_device }}
|
||||||
|
Video Device: {{ video_device }}
|
||||||
|
|
||||||
|
- name: video_capture - streamer - copy service script
|
||||||
|
template:
|
||||||
|
src: stream_service.sh.j2
|
||||||
|
dest: "{{ streaming_working_folder }}/stream_service.sh"
|
||||||
|
mode: 0755
|
||||||
|
|
||||||
|
- name: video_capture - streamer - create service file
|
||||||
|
template:
|
||||||
|
src: stream_service.service.j2
|
||||||
|
dest: /etc/systemd/system/stream_service.service
|
||||||
|
mode: 0644
|
||||||
|
|
||||||
|
# daemon reload
|
||||||
|
- name: video_capture - streamer - daemon reload
|
||||||
|
systemd:
|
||||||
|
daemon_reload: yes
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
...
|
||||||
86
templates/app-service.py.j2
Normal file
86
templates/app-service.py.j2
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import subprocess
|
||||||
|
import requests
|
||||||
|
from lxml import html
|
||||||
|
from flask import Flask, request, jsonify
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
def start_service():
|
||||||
|
command = "systemctl start {{ service_control_name }}"
|
||||||
|
try:
|
||||||
|
# Run the command using subprocess.run()
|
||||||
|
process = subprocess.Popen(command, shell=True)
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
return {"Error": e.strip('\n')}
|
||||||
|
|
||||||
|
command = "systemctl status {{ service_control_name }} | grep Active | cut -d ':' -f 2- | cut -b 2-"
|
||||||
|
try:
|
||||||
|
# Run the command using subprocess.run()
|
||||||
|
result = subprocess.run(command, shell=True, check=True, capture_output=True, text=True)
|
||||||
|
return {"Message": result.stdout.strip('\n')}
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
return {"Error": e.strip('\n')}
|
||||||
|
|
||||||
|
def stop_service():
|
||||||
|
command = "systemctl stop {{ service_control_name }}"
|
||||||
|
try:
|
||||||
|
# Run the command using subprocess.run()
|
||||||
|
process = subprocess.Popen(command, shell=True)
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
return {"Error": e.strip('\n')}
|
||||||
|
|
||||||
|
command = "systemctl status {{ service_control_name }} | grep Active | cut -d ':' -f 2- | cut -b 2-"
|
||||||
|
try:
|
||||||
|
# Run the command using subprocess.run()
|
||||||
|
result = subprocess.run(command, shell=True, check=True, capture_output=True, text=True)
|
||||||
|
return {"Message": result.stdout.strip('\n')}
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
return {"Error": e.strip('\n')}
|
||||||
|
|
||||||
|
def service_status():
|
||||||
|
command = "systemctl status {{ service_control_name }} | grep Active | cut -d ':' -f 2- | cut -b 2-"
|
||||||
|
try:
|
||||||
|
# Run the command using subprocess.run()
|
||||||
|
result = subprocess.run(command, shell=True, check=True, capture_output=True, text=True)
|
||||||
|
command = "systemctl status {{ service_control_name }} | grep Active | cut -d ':' -f 2 | cut -d ' ' -f 2"
|
||||||
|
status = subprocess.run(command, shell=True, check=True, capture_output=True, text=True)
|
||||||
|
return {"Message": result.stdout.strip('\n'), "Status": status.stdout.strip('\n')}
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
return {"Error": e.strip('\n')}
|
||||||
|
|
||||||
|
@app.route('/start', methods=['GET'])
|
||||||
|
def start():
|
||||||
|
try:
|
||||||
|
return jsonify(start_service())
|
||||||
|
except ValueError as e:
|
||||||
|
print(e)
|
||||||
|
return jsonify({'error': e}), 400
|
||||||
|
|
||||||
|
@app.route('/stop', methods=['GET'])
|
||||||
|
def stop():
|
||||||
|
try:
|
||||||
|
return jsonify(stop_service())
|
||||||
|
except ValueError as e:
|
||||||
|
print(e)
|
||||||
|
return jsonify({'error': e}), 400
|
||||||
|
|
||||||
|
@app.route('/status', methods=['GET'])
|
||||||
|
def status():
|
||||||
|
try:
|
||||||
|
return jsonify(service_status())
|
||||||
|
except ValueError as e:
|
||||||
|
print(e)
|
||||||
|
return jsonify({'error': e}), 400
|
||||||
|
|
||||||
|
@app.route('/test', methods=['GET'])
|
||||||
|
def test():
|
||||||
|
return jsonify({'message': 'Hello there'})
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(debug=True, host='0.0.0.0', port=5000)
|
||||||
|
|
||||||
12
templates/docker-compose-php.yaml.j2
Normal file
12
templates/docker-compose-php.yaml.j2
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
services:
|
||||||
|
|
||||||
|
{{ container_name }}:
|
||||||
|
container_name: {{ container_name }}
|
||||||
|
image: php:8.0-apache
|
||||||
|
ports:
|
||||||
|
- {{ container_http_port }}:80
|
||||||
|
volumes:
|
||||||
|
- ./html:/var/www/html/
|
||||||
|
{{ extra_volumes }}
|
||||||
|
network_mode: bridge
|
||||||
|
restart: always
|
||||||
775
templates/mediamtx.yml.j2
Normal file
775
templates/mediamtx.yml.j2
Normal file
@ -0,0 +1,775 @@
|
|||||||
|
###############################################
|
||||||
|
# Global settings
|
||||||
|
|
||||||
|
# Settings in this section are applied anywhere.
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
# Global settings -> General
|
||||||
|
|
||||||
|
# Verbosity of the program; available values are "error", "warn", "info", "debug".
|
||||||
|
logLevel: info
|
||||||
|
# Destinations of log messages; available values are "stdout", "file" and "syslog".
|
||||||
|
logDestinations: [stdout]
|
||||||
|
# If "file" is in logDestinations, this is the file which will receive the logs.
|
||||||
|
logFile: mediamtx.log
|
||||||
|
# If "syslog" is in logDestinations, use prefix for logs.
|
||||||
|
sysLogPrefix: mediamtx
|
||||||
|
|
||||||
|
# Timeout of read operations.
|
||||||
|
readTimeout: 10s
|
||||||
|
# Timeout of write operations.
|
||||||
|
writeTimeout: 10s
|
||||||
|
# Size of the queue of outgoing packets.
|
||||||
|
# A higher value allows to increase throughput, a lower value allows to save RAM.
|
||||||
|
writeQueueSize: 512
|
||||||
|
# Maximum size of outgoing UDP packets.
|
||||||
|
# This can be decreased to avoid fragmentation on networks with a low UDP MTU.
|
||||||
|
udpMaxPayloadSize: 1472
|
||||||
|
|
||||||
|
# Command to run when a client connects to the server.
|
||||||
|
# This is terminated with SIGINT when a client disconnects from the server.
|
||||||
|
# The following environment variables are available:
|
||||||
|
# * MTX_CONN_TYPE: connection type
|
||||||
|
# * MTX_CONN_ID: connection ID
|
||||||
|
# * RTSP_PORT: RTSP server port
|
||||||
|
runOnConnect:
|
||||||
|
# Restart the command if it exits.
|
||||||
|
runOnConnectRestart: no
|
||||||
|
# Command to run when a client disconnects from the server.
|
||||||
|
# Environment variables are the same of runOnConnect.
|
||||||
|
runOnDisconnect:
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
# Global settings -> Authentication
|
||||||
|
|
||||||
|
# Authentication method. Available values are:
|
||||||
|
# * internal: users are stored in the configuration file
|
||||||
|
# * http: an external HTTP URL is contacted to perform authentication
|
||||||
|
# * jwt: an external identity server provides authentication through JWTs
|
||||||
|
authMethod: internal
|
||||||
|
|
||||||
|
# Internal authentication.
|
||||||
|
# list of users.
|
||||||
|
authInternalUsers:
|
||||||
|
# Default unprivileged user.
|
||||||
|
# Username. 'any' means any user, including anonymous ones.
|
||||||
|
- user: any
|
||||||
|
# Password. Not used in case of 'any' user.
|
||||||
|
pass:
|
||||||
|
# IPs or networks allowed to use this user. An empty list means any IP.
|
||||||
|
ips: []
|
||||||
|
# List of permissions.
|
||||||
|
permissions:
|
||||||
|
# Available actions are: publish, read, playback, api, metrics, pprof.
|
||||||
|
- action: publish
|
||||||
|
# Paths can be set to further restrict access to a specific path.
|
||||||
|
# An empty path means any path.
|
||||||
|
# Regular expressions can be used by using a tilde as prefix.
|
||||||
|
path:
|
||||||
|
- action: read
|
||||||
|
path:
|
||||||
|
- action: playback
|
||||||
|
path:
|
||||||
|
|
||||||
|
# Default administrator.
|
||||||
|
# This allows to use API, metrics and PPROF without authentication,
|
||||||
|
# if the IP is localhost.
|
||||||
|
- user: any
|
||||||
|
pass:
|
||||||
|
ips: ['127.0.0.1', '::1']
|
||||||
|
permissions:
|
||||||
|
- action: api
|
||||||
|
- action: metrics
|
||||||
|
- action: pprof
|
||||||
|
|
||||||
|
# HTTP-based authentication.
|
||||||
|
# URL called to perform authentication. Every time a user wants
|
||||||
|
# to authenticate, the server calls this URL with the POST method
|
||||||
|
# and a body containing:
|
||||||
|
# {
|
||||||
|
# "user": "user",
|
||||||
|
# "password": "password",
|
||||||
|
# "token": "token",
|
||||||
|
# "ip": "ip",
|
||||||
|
# "action": "publish|read|playback|api|metrics|pprof",
|
||||||
|
# "path": "path",
|
||||||
|
# "protocol": "rtsp|rtmp|hls|webrtc|srt",
|
||||||
|
# "id": "id",
|
||||||
|
# "query": "query"
|
||||||
|
# }
|
||||||
|
# If the response code is 20x, authentication is accepted, otherwise
|
||||||
|
# it is discarded.
|
||||||
|
authHTTPAddress:
|
||||||
|
# Actions to exclude from HTTP-based authentication.
|
||||||
|
# Format is the same as the one of user permissions.
|
||||||
|
authHTTPExclude:
|
||||||
|
- action: api
|
||||||
|
- action: metrics
|
||||||
|
- action: pprof
|
||||||
|
|
||||||
|
# JWT-based authentication.
|
||||||
|
# Users have to login through an external identity server and obtain a JWT.
|
||||||
|
# This JWT must contain the claim "mediamtx_permissions" with permissions,
|
||||||
|
# for instance:
|
||||||
|
# {
|
||||||
|
# "mediamtx_permissions": [
|
||||||
|
# {
|
||||||
|
# "action": "publish",
|
||||||
|
# "path": "somepath"
|
||||||
|
# }
|
||||||
|
# ]
|
||||||
|
# }
|
||||||
|
# Users are expected to pass the JWT in the Authorization header, password or query parameter.
|
||||||
|
# This is the JWKS URL that will be used to pull (once) the public key that allows
|
||||||
|
# to validate JWTs.
|
||||||
|
authJWTJWKS:
|
||||||
|
# If the JWKS URL has a self-signed or invalid certificate,
|
||||||
|
# you can provide the fingerprint of the certificate in order to
|
||||||
|
# validate it anyway. It can be obtained by running:
|
||||||
|
# openssl s_client -connect jwt_jwks_domain:443 </dev/null 2>/dev/null | sed -n '/BEGIN/,/END/p' > server.crt
|
||||||
|
# openssl x509 -in server.crt -noout -fingerprint -sha256 | cut -d "=" -f2 | tr -d ':'
|
||||||
|
authJWTJWKSFingerprint:
|
||||||
|
# name of the claim that contains permissions.
|
||||||
|
authJWTClaimKey: mediamtx_permissions
|
||||||
|
# Actions to exclude from JWT-based authentication.
|
||||||
|
# Format is the same as the one of user permissions.
|
||||||
|
authJWTExclude: []
|
||||||
|
# allow passing the JWT through query parameters of HTTP requests (i.e. ?jwt=JWT).
|
||||||
|
# This is a security risk.
|
||||||
|
authJWTInHTTPQuery: true
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
# Global settings -> Control API
|
||||||
|
|
||||||
|
# Enable controlling the server through the Control API.
|
||||||
|
api: no
|
||||||
|
# Address of the Control API listener.
|
||||||
|
apiAddress: :9997
|
||||||
|
# Enable TLS/HTTPS on the Control API server.
|
||||||
|
apiEncryption: no
|
||||||
|
# Path to the server key. This is needed only when encryption is yes.
|
||||||
|
# This can be generated with:
|
||||||
|
# openssl genrsa -out server.key 2048
|
||||||
|
# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650
|
||||||
|
apiServerKey: server.key
|
||||||
|
# Path to the server certificate.
|
||||||
|
apiServerCert: server.crt
|
||||||
|
# Value of the Access-Control-Allow-Origin header provided in every HTTP response.
|
||||||
|
apiAllowOrigin: '*'
|
||||||
|
# List of IPs or CIDRs of proxies placed before the HTTP server.
|
||||||
|
# If the server receives a request from one of these entries, IP in logs
|
||||||
|
# will be taken from the X-Forwarded-For header.
|
||||||
|
apiTrustedProxies: []
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
# Global settings -> Metrics
|
||||||
|
|
||||||
|
# Enable Prometheus-compatible metrics.
|
||||||
|
metrics: no
|
||||||
|
# Address of the metrics HTTP listener.
|
||||||
|
metricsAddress: :9998
|
||||||
|
# Enable TLS/HTTPS on the Metrics server.
|
||||||
|
metricsEncryption: no
|
||||||
|
# Path to the server key. This is needed only when encryption is yes.
|
||||||
|
# This can be generated with:
|
||||||
|
# openssl genrsa -out server.key 2048
|
||||||
|
# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650
|
||||||
|
metricsServerKey: server.key
|
||||||
|
# Path to the server certificate.
|
||||||
|
metricsServerCert: server.crt
|
||||||
|
# Value of the Access-Control-Allow-Origin header provided in every HTTP response.
|
||||||
|
metricsAllowOrigin: '*'
|
||||||
|
# List of IPs or CIDRs of proxies placed before the HTTP server.
|
||||||
|
# If the server receives a request from one of these entries, IP in logs
|
||||||
|
# will be taken from the X-Forwarded-For header.
|
||||||
|
metricsTrustedProxies: []
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
# Global settings -> PPROF
|
||||||
|
|
||||||
|
# Enable pprof-compatible endpoint to monitor performances.
|
||||||
|
pprof: no
|
||||||
|
# Address of the pprof listener.
|
||||||
|
pprofAddress: :9999
|
||||||
|
# Enable TLS/HTTPS on the pprof server.
|
||||||
|
pprofEncryption: no
|
||||||
|
# Path to the server key. This is needed only when encryption is yes.
|
||||||
|
# This can be generated with:
|
||||||
|
# openssl genrsa -out server.key 2048
|
||||||
|
# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650
|
||||||
|
pprofServerKey: server.key
|
||||||
|
# Path to the server certificate.
|
||||||
|
pprofServerCert: server.crt
|
||||||
|
# Value of the Access-Control-Allow-Origin header provided in every HTTP response.
|
||||||
|
pprofAllowOrigin: '*'
|
||||||
|
# List of IPs or CIDRs of proxies placed before the HTTP server.
|
||||||
|
# If the server receives a request from one of these entries, IP in logs
|
||||||
|
# will be taken from the X-Forwarded-For header.
|
||||||
|
pprofTrustedProxies: []
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
# Global settings -> Playback server
|
||||||
|
|
||||||
|
# Enable downloading recordings from the playback server.
|
||||||
|
playback: no
|
||||||
|
# Address of the playback server listener.
|
||||||
|
playbackAddress: :9996
|
||||||
|
# Enable TLS/HTTPS on the playback server.
|
||||||
|
playbackEncryption: no
|
||||||
|
# Path to the server key. This is needed only when encryption is yes.
|
||||||
|
# This can be generated with:
|
||||||
|
# openssl genrsa -out server.key 2048
|
||||||
|
# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650
|
||||||
|
playbackServerKey: server.key
|
||||||
|
# Path to the server certificate.
|
||||||
|
playbackServerCert: server.crt
|
||||||
|
# Value of the Access-Control-Allow-Origin header provided in every HTTP response.
|
||||||
|
playbackAllowOrigin: '*'
|
||||||
|
# List of IPs or CIDRs of proxies placed before the HTTP server.
|
||||||
|
# If the server receives a request from one of these entries, IP in logs
|
||||||
|
# will be taken from the X-Forwarded-For header.
|
||||||
|
playbackTrustedProxies: []
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
# Global settings -> RTSP server
|
||||||
|
|
||||||
|
# Enable publishing and reading streams with the RTSP protocol.
|
||||||
|
rtsp: yes
|
||||||
|
# List of enabled RTSP transport protocols.
|
||||||
|
# UDP is the most performant, but doesn't work when there's a NAT/firewall between
|
||||||
|
# server and clients.
|
||||||
|
# UDP-multicast allows to save bandwidth when clients are all in the same LAN.
|
||||||
|
# TCP is the most versatile.
|
||||||
|
# The handshake is always performed with TCP.
|
||||||
|
rtspTransports: [udp, multicast, tcp]
|
||||||
|
# Use secure protocol variants (RTSPS, TLS, SRTP).
|
||||||
|
# Available values are "no", "strict", "optional".
|
||||||
|
rtspEncryption: "no"
|
||||||
|
# Address of the TCP/RTSP listener. This is needed only when encryption is "no" or "optional".
|
||||||
|
rtspAddress: :8554
|
||||||
|
# Address of the TCP/TLS/RTSPS listener. This is needed only when encryption is "strict" or "optional".
|
||||||
|
rtspsAddress: :8322
|
||||||
|
# Address of the UDP/RTP listener. This is needed only when "udp" is in rtspTransports.
|
||||||
|
rtpAddress: :8000
|
||||||
|
# Address of the UDP/RTCP listener. This is needed only when "udp" is in rtspTransports.
|
||||||
|
rtcpAddress: :8001
|
||||||
|
# IP range of all UDP-multicast listeners. This is needed only when "multicast" is in rtspTransports.
|
||||||
|
multicastIPRange: 224.1.0.0/16
|
||||||
|
# Port of all UDP-multicast/RTP listeners. This is needed only when "multicast" is in rtspTransports.
|
||||||
|
multicastRTPPort: 8002
|
||||||
|
# Port of all UDP-multicast/RTCP listeners. This is needed only when "multicast" is in rtspTransports.
|
||||||
|
multicastRTCPPort: 8003
|
||||||
|
# Address of the UDP/SRTP listener. This is needed only when "udp" is in rtspTransports and encryption is enabled.
|
||||||
|
srtpAddress: :8004
|
||||||
|
# Address of the UDP/SRTCP listener. This is needed only when "udp" is in rtspTransports and encryption is enabled.
|
||||||
|
srtcpAddress: :8005
|
||||||
|
# Port of all UDP-multicast/SRTP listeners. This is needed only when "multicast" is in rtspTransports and encryption is enabled.
|
||||||
|
multicastSRTPPort: 8006
|
||||||
|
# Port of all UDP-multicast/SRTCP listeners. This is needed only when "multicast" is in rtspTransports and encryption is enabled.
|
||||||
|
multicastSRTCPPort: 8007
|
||||||
|
# Path to the server key. This is needed only when encryption is "strict" or "optional".
|
||||||
|
# This can be generated with:
|
||||||
|
# openssl genrsa -out server.key 2048
|
||||||
|
# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650
|
||||||
|
rtspServerKey: server.key
|
||||||
|
# Path to the server certificate. This is needed only when encryption is "strict" or "optional".
|
||||||
|
rtspServerCert: server.crt
|
||||||
|
# Authentication methods. Available are "basic" and "digest".
|
||||||
|
# "digest" doesn't provide any additional security and is available for compatibility only.
|
||||||
|
rtspAuthMethods: [basic]
|
||||||
|
# Size of the UDP buffer of the RTSP server.
|
||||||
|
# This can be increased to mitigate packet losses.
|
||||||
|
# It defaults to the default value of the operating system.
|
||||||
|
rtspUDPReadBufferSize: 0
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
# Global settings -> RTMP server
|
||||||
|
|
||||||
|
# Enable publishing and reading streams with the RTMP protocol.
|
||||||
|
rtmp: yes
|
||||||
|
# Address of the RTMP listener. This is needed only when encryption is "no" or "optional".
|
||||||
|
rtmpAddress: :1935
|
||||||
|
# Encrypt connections with TLS (RTMPS).
|
||||||
|
# Available values are "no", "strict", "optional".
|
||||||
|
rtmpEncryption: "no"
|
||||||
|
# Address of the RTMPS listener. This is needed only when encryption is "strict" or "optional".
|
||||||
|
rtmpsAddress: :1936
|
||||||
|
# Path to the server key. This is needed only when encryption is "strict" or "optional".
|
||||||
|
# This can be generated with:
|
||||||
|
# openssl genrsa -out server.key 2048
|
||||||
|
# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650
|
||||||
|
rtmpServerKey: server.key
|
||||||
|
# Path to the server certificate. This is needed only when encryption is "strict" or "optional".
|
||||||
|
rtmpServerCert: server.crt
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
# Global settings -> HLS server
|
||||||
|
|
||||||
|
# Enable reading streams with the HLS protocol.
|
||||||
|
hls: yes
|
||||||
|
# Address of the HLS listener.
|
||||||
|
hlsAddress: :8888
|
||||||
|
# Enable TLS/HTTPS on the HLS server.
|
||||||
|
# This is required for Low-Latency HLS.
|
||||||
|
hlsEncryption: no
|
||||||
|
# Path to the server key. This is needed only when encryption is yes.
|
||||||
|
# This can be generated with:
|
||||||
|
# openssl genrsa -out server.key 2048
|
||||||
|
# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650
|
||||||
|
hlsServerKey: server.key
|
||||||
|
# Path to the server certificate.
|
||||||
|
hlsServerCert: server.crt
|
||||||
|
# Value of the Access-Control-Allow-Origin header provided in every HTTP response.
|
||||||
|
# This allows to play the HLS stream from an external website.
|
||||||
|
hlsAllowOrigin: '*'
|
||||||
|
# List of IPs or CIDRs of proxies placed before the HLS server.
|
||||||
|
# If the server receives a request from one of these entries, IP in logs
|
||||||
|
# will be taken from the X-Forwarded-For header.
|
||||||
|
hlsTrustedProxies: []
|
||||||
|
# By default, HLS is generated only when requested by a user.
|
||||||
|
# This option allows to generate it always, avoiding the delay between request and generation.
|
||||||
|
hlsAlwaysRemux: no
|
||||||
|
# Variant of the HLS protocol to use. Available options are:
|
||||||
|
# * mpegts - uses MPEG-TS segments, for maximum compatibility.
|
||||||
|
# * fmp4 - uses fragmented MP4 segments, more efficient.
|
||||||
|
# * lowLatency - uses Low-Latency HLS.
|
||||||
|
hlsVariant: lowLatency
|
||||||
|
# Number of HLS segments to keep on the server.
|
||||||
|
# Segments allow to seek through the stream.
|
||||||
|
# Their number doesn't influence latency.
|
||||||
|
hlsSegmentCount: 7
|
||||||
|
# Minimum duration of each segment.
|
||||||
|
# A player usually puts 3 segments in a buffer before reproducing the stream.
|
||||||
|
# The final segment duration is also influenced by the interval between IDR frames,
|
||||||
|
# since the server changes the duration in order to include at least one IDR frame
|
||||||
|
# in each segment.
|
||||||
|
hlsSegmentDuration: 1s
|
||||||
|
# Minimum duration of each part.
|
||||||
|
# A player usually puts 3 parts in a buffer before reproducing the stream.
|
||||||
|
# Parts are used in Low-Latency HLS in place of segments.
|
||||||
|
# Part duration is influenced by the distance between video/audio samples
|
||||||
|
# and is adjusted in order to produce segments with a similar duration.
|
||||||
|
hlsPartDuration: 200ms
|
||||||
|
# Maximum size of each segment.
|
||||||
|
# This prevents RAM exhaustion.
|
||||||
|
hlsSegmentMaxSize: 50M
|
||||||
|
# Directory in which to save segments, instead of keeping them in the RAM.
|
||||||
|
# This decreases performance, since reading from disk is less performant than
|
||||||
|
# reading from RAM, but allows to save RAM.
|
||||||
|
hlsDirectory: ''
|
||||||
|
# The muxer will be closed when there are no
|
||||||
|
# reader requests and this amount of time has passed.
|
||||||
|
hlsMuxerCloseAfter: 60s
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
# Global settings -> WebRTC server
|
||||||
|
|
||||||
|
# Enable publishing and reading streams with the WebRTC protocol.
|
||||||
|
webrtc: yes
|
||||||
|
# Address of the WebRTC HTTP listener.
|
||||||
|
webrtcAddress: :8889
|
||||||
|
# Enable TLS/HTTPS on the WebRTC server.
|
||||||
|
webrtcEncryption: no
|
||||||
|
# Path to the server key.
|
||||||
|
# This can be generated with:
|
||||||
|
# openssl genrsa -out server.key 2048
|
||||||
|
# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650
|
||||||
|
webrtcServerKey: server.key
|
||||||
|
# Path to the server certificate.
|
||||||
|
webrtcServerCert: server.crt
|
||||||
|
# Value of the Access-Control-Allow-Origin header provided in every HTTP response.
|
||||||
|
# This allows to play the WebRTC stream from an external website.
|
||||||
|
webrtcAllowOrigin: '*'
|
||||||
|
# List of IPs or CIDRs of proxies placed before the WebRTC server.
|
||||||
|
# If the server receives a request from one of these entries, IP in logs
|
||||||
|
# will be taken from the X-Forwarded-For header.
|
||||||
|
webrtcTrustedProxies: []
|
||||||
|
# Address of a local UDP listener that will receive connections.
|
||||||
|
# Use a blank string to disable.
|
||||||
|
webrtcLocalUDPAddress: :8189
|
||||||
|
# Address of a local TCP listener that will receive connections.
|
||||||
|
# This is disabled by default since TCP is less efficient than UDP and
|
||||||
|
# introduces a progressive delay when network is congested.
|
||||||
|
webrtcLocalTCPAddress: ''
|
||||||
|
# WebRTC clients need to know the IP of the server.
|
||||||
|
# Gather IPs from interfaces and send them to clients.
|
||||||
|
webrtcIPsFromInterfaces: yes
|
||||||
|
# List of interfaces whose IPs will be sent to clients.
|
||||||
|
# An empty value means to use all available interfaces.
|
||||||
|
webrtcIPsFromInterfacesList: []
|
||||||
|
# List of additional hosts or IPs to send to clients.
|
||||||
|
webrtcAdditionalHosts: []
|
||||||
|
# ICE servers. Needed only when local listeners can't be reached by clients.
|
||||||
|
# STUN servers allows to obtain and share the public IP of the server.
|
||||||
|
# TURN/TURNS servers forces all traffic through them.
|
||||||
|
webrtcICEServers2: []
|
||||||
|
# - url: stun:stun.l.google.com:19302
|
||||||
|
# if user is "AUTH_SECRET", then authentication is secret based.
|
||||||
|
# the secret must be inserted into the password field.
|
||||||
|
# username: ''
|
||||||
|
# password: ''
|
||||||
|
# clientOnly: false
|
||||||
|
# Time to wait for the WebRTC handshake to complete.
|
||||||
|
webrtcHandshakeTimeout: 10s
|
||||||
|
# Maximum time to gather video tracks.
|
||||||
|
webrtcTrackGatherTimeout: 2s
|
||||||
|
# The maximum time to gather STUN candidates.
|
||||||
|
webrtcSTUNGatherTimeout: 5s
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
# Global settings -> SRT server
|
||||||
|
|
||||||
|
# Enable publishing and reading streams with the SRT protocol.
|
||||||
|
srt: yes
|
||||||
|
# Address of the SRT listener.
|
||||||
|
srtAddress: :8890
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
# Default path settings
|
||||||
|
|
||||||
|
# Settings in "pathDefaults" are applied anywhere,
|
||||||
|
# unless they are overridden in "paths".
|
||||||
|
pathDefaults:
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
# Default path settings -> General
|
||||||
|
|
||||||
|
# Source of the stream. This can be:
|
||||||
|
# * publisher -> the stream is provided by a RTSP, RTMP, WebRTC or SRT client
|
||||||
|
# * rtsp://existing-url -> the stream is pulled from another RTSP server / camera
|
||||||
|
# * rtsps://existing-url -> the stream is pulled from another RTSP server / camera with RTSPS
|
||||||
|
# * rtmp://existing-url -> the stream is pulled from another RTMP server / camera
|
||||||
|
# * rtmps://existing-url -> the stream is pulled from another RTMP server / camera with RTMPS
|
||||||
|
# * http://existing-url/stream.m3u8 -> the stream is pulled from another HLS server / camera
|
||||||
|
# * https://existing-url/stream.m3u8 -> the stream is pulled from another HLS server / camera with HTTPS
|
||||||
|
# * udp+mpegts://ip:port -> the stream is pulled from MPEG-TS over UDP, by listening on the specified address
|
||||||
|
# * unix+mpegts://socket -> the stream is pulled from MPEG-TS over Unix socket, by using the socket
|
||||||
|
# * udp+rtp://ip:port -> the stream is pulled from RTP over UDP, by listening on the specified address
|
||||||
|
# * unix+rtp://socket -> the stream is pulled from RTP over Unix socket, by using the socket
|
||||||
|
# * srt://existing-url -> the stream is pulled from another SRT server / camera
|
||||||
|
# * whep://existing-url -> the stream is pulled from another WebRTC server / camera
|
||||||
|
# * wheps://existing-url -> the stream is pulled from another WebRTC server / camera with HTTPS
|
||||||
|
# * redirect -> the stream is provided by another path or server
|
||||||
|
# * rpiCamera -> the stream is provided by a Raspberry Pi Camera
|
||||||
|
# The following variables can be used in the source string:
|
||||||
|
# * $MTX_QUERY: query parameters (passed by first reader)
|
||||||
|
# * $G1, $G2, ...: regular expression groups, if path name is
|
||||||
|
# a regular expression.
|
||||||
|
source: publisher
|
||||||
|
# If the source is a URL, and the source certificate is self-signed
|
||||||
|
# or invalid, you can provide the fingerprint of the certificate in order to
|
||||||
|
# validate it anyway. It can be obtained by running:
|
||||||
|
# openssl s_client -connect source_ip:source_port </dev/null 2>/dev/null | sed -n '/BEGIN/,/END/p' > server.crt
|
||||||
|
# openssl x509 -in server.crt -noout -fingerprint -sha256 | cut -d "=" -f2 | tr -d ':'
|
||||||
|
sourceFingerprint:
|
||||||
|
# If the source is a URL, it will be pulled only when at least
|
||||||
|
# one reader is connected, saving bandwidth.
|
||||||
|
sourceOnDemand: no
|
||||||
|
# If sourceOnDemand is "yes", readers will be put on hold until the source is
|
||||||
|
# ready or until this amount of time has passed.
|
||||||
|
sourceOnDemandStartTimeout: 10s
|
||||||
|
# If sourceOnDemand is "yes", the source will be closed when there are no
|
||||||
|
# readers connected and this amount of time has passed.
|
||||||
|
sourceOnDemandCloseAfter: 10s
|
||||||
|
# Maximum number of readers. Zero means no limit.
|
||||||
|
maxReaders: 0
|
||||||
|
# SRT encryption passphrase required to read from this path.
|
||||||
|
srtReadPassphrase:
|
||||||
|
# If the stream is not available, redirect readers to this path.
|
||||||
|
# It can be can be a relative path (i.e. /otherstream) or an absolute RTSP URL.
|
||||||
|
fallback:
|
||||||
|
# Route original absolute timestamps of RTSP and WebRTC frames, instead of replacing them.
|
||||||
|
useAbsoluteTimestamp: false
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
# Default path settings -> Record
|
||||||
|
|
||||||
|
# Record streams to disk.
|
||||||
|
record: yes
|
||||||
|
|
||||||
|
# Path of recording segments.
|
||||||
|
# Extension is added automatically.
|
||||||
|
# Available variables are %path (path name), %Y %m %d (year, month, day),
|
||||||
|
# %H %M %S (hours, minutes, seconds), %f (microseconds), %z (time zone), %s (unix epoch).
|
||||||
|
recordPath: {{ recording_capture_folder }}/%path/%Y-%m-%d_%H-%M-%S-%f
|
||||||
|
|
||||||
|
# Available formats are "fmp4" (fragmented MP4) and "mpegts" (MPEG-TS).
|
||||||
|
recordFormat: fmp4
|
||||||
|
|
||||||
|
# fMP4 segments are concatenation of small MP4 files (parts), each with this duration.
|
||||||
|
# MPEG-TS segments are concatenation of 188-bytes packets, flushed to disk with this period.
|
||||||
|
# When a system failure occurs, the last part gets lost.
|
||||||
|
# Therefore, the part duration is equal to the RPO (recovery point objective).
|
||||||
|
recordPartDuration: 1s
|
||||||
|
|
||||||
|
# This prevents RAM exhaustion.
|
||||||
|
recordMaxPartSize: 5000M
|
||||||
|
|
||||||
|
# Minimum duration of each segment.
|
||||||
|
recordSegmentDuration: 3600s
|
||||||
|
|
||||||
|
# Delete segments after this timespan.
|
||||||
|
# Set to 0s to disable automatic deletion.
|
||||||
|
recordDeleteAfter: 0s
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
# Default path settings -> Publisher source (when source is "publisher")
|
||||||
|
|
||||||
|
# Allow another client to disconnect the current publisher and publish in its place.
|
||||||
|
overridePublisher: yes
|
||||||
|
# SRT encryption passphrase required to publish to this path.
|
||||||
|
srtPublishPassphrase:
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
# Default path settings -> RTSP source (when source is a RTSP or a RTSPS URL)
|
||||||
|
|
||||||
|
# Transport protocol used to pull the stream. available values are "automatic", "udp", "multicast", "tcp".
|
||||||
|
rtspTransport: automatic
|
||||||
|
# Support sources that don't provide server ports or use random server ports. This is a security issue
|
||||||
|
# and must be used only when interacting with sources that require it.
|
||||||
|
rtspAnyPort: no
|
||||||
|
# Range header to send to the source, in order to start streaming from the specified offset.
|
||||||
|
# available values:
|
||||||
|
# * clock: Absolute time
|
||||||
|
# * npt: Normal Play Time
|
||||||
|
# * smpte: SMPTE timestamps relative to the start of the recording
|
||||||
|
rtspRangeType:
|
||||||
|
# Available values:
|
||||||
|
# * clock: UTC ISO 8601 combined date and time string, e.g. 20230812T120000Z
|
||||||
|
# * npt: duration such as "300ms", "1.5m" or "2h45m", valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h"
|
||||||
|
# * smpte: duration such as "300ms", "1.5m" or "2h45m", valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h"
|
||||||
|
rtspRangeStart:
|
||||||
|
# Size of the UDP buffer of the RTSP client.
|
||||||
|
# This can be increased to mitigate packet losses.
|
||||||
|
# It defaults to the default value of the operating system.
|
||||||
|
rtspUDPReadBufferSize: 0
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
# Default path settings -> MPEG-TS source (when source is MPEG-TS)
|
||||||
|
|
||||||
|
# Size of the UDP buffer of the MPEG-TS client.
|
||||||
|
# This can be increased to mitigate packet losses.
|
||||||
|
# It defaults to the default value of the operating system.
|
||||||
|
mpegtsUDPReadBufferSize: 0
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
# Default path settings -> RTP source (when source is RTP)
|
||||||
|
|
||||||
|
# session description protocol (SDP) of the RTP stream.
|
||||||
|
rtpSDP:
|
||||||
|
# Size of the UDP buffer of the RTP client.
|
||||||
|
# This can be increased to mitigate packet losses.
|
||||||
|
# It defaults to the default value of the operating system.
|
||||||
|
rtpUDPReadBufferSize: 0
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
# Default path settings -> Redirect source (when source is "redirect")
|
||||||
|
|
||||||
|
# path which clients will be redirected to.
|
||||||
|
# It can be can be a relative path (i.e. /otherstream) or an absolute RTSP URL.
|
||||||
|
sourceRedirect:
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
# Default path settings -> Raspberry Pi Camera source (when source is "rpiCamera")
|
||||||
|
|
||||||
|
# ID of the camera.
|
||||||
|
rpiCameraCamID: 0
|
||||||
|
# Whether this is a secondary stream.
|
||||||
|
rpiCameraSecondary: false
|
||||||
|
# Width of frames.
|
||||||
|
rpiCameraWidth: 1920
|
||||||
|
# Height of frames.
|
||||||
|
rpiCameraHeight: 1080
|
||||||
|
# Flip horizontally.
|
||||||
|
rpiCameraHFlip: false
|
||||||
|
# Flip vertically.
|
||||||
|
rpiCameraVFlip: false
|
||||||
|
# Brightness [-1, 1].
|
||||||
|
rpiCameraBrightness: 0
|
||||||
|
# Contrast [0, 16].
|
||||||
|
rpiCameraContrast: 1
|
||||||
|
# Saturation [0, 16].
|
||||||
|
rpiCameraSaturation: 1
|
||||||
|
# Sharpness [0, 16].
|
||||||
|
rpiCameraSharpness: 1
|
||||||
|
# Exposure mode.
|
||||||
|
# values: normal, short, long, custom.
|
||||||
|
rpiCameraExposure: normal
|
||||||
|
# Auto-white-balance mode.
|
||||||
|
# (auto, incandescent, tungsten, fluorescent, indoor, daylight, cloudy or custom).
|
||||||
|
rpiCameraAWB: auto
|
||||||
|
# Auto-white-balance fixed gains. This can be used in place of rpiCameraAWB.
|
||||||
|
# format: [red,blue].
|
||||||
|
rpiCameraAWBGains: [0, 0]
|
||||||
|
# Denoise operating mode (off, cdn_off, cdn_fast, cdn_hq).
|
||||||
|
rpiCameraDenoise: "off"
|
||||||
|
# Fixed shutter speed, in microseconds.
|
||||||
|
rpiCameraShutter: 0
|
||||||
|
# Metering mode of the AEC/AGC algorithm (centre, spot, matrix or custom).
|
||||||
|
rpiCameraMetering: centre
|
||||||
|
# Fixed gain.
|
||||||
|
rpiCameraGain: 0
|
||||||
|
# EV compensation of the image in range [-10, 10].
|
||||||
|
rpiCameraEV: 0
|
||||||
|
# Region of interest, in format x,y,width,height (all normalized between 0 and 1).
|
||||||
|
rpiCameraROI:
|
||||||
|
# Whether to enable HDR on Raspberry Camera 3.
|
||||||
|
rpiCameraHDR: false
|
||||||
|
# Tuning file.
|
||||||
|
rpiCameraTuningFile:
|
||||||
|
# Sensor mode, in format [width]:[height]:[bit-depth]:[packing]
|
||||||
|
# bit-depth and packing are optional.
|
||||||
|
rpiCameraMode:
|
||||||
|
# frames per second.
|
||||||
|
rpiCameraFPS: 30
|
||||||
|
# Autofocus mode (auto, manual or continuous).
|
||||||
|
rpiCameraAfMode: continuous
|
||||||
|
# Autofocus range (normal, macro or full).
|
||||||
|
rpiCameraAfRange: normal
|
||||||
|
# Autofocus speed (normal or fast).
|
||||||
|
rpiCameraAfSpeed: normal
|
||||||
|
# Lens position (for manual autofocus only), will be set to focus to a specific distance
|
||||||
|
# calculated by the following formula: d = 1 / value
|
||||||
|
# Examples: 0 moves the lens to infinity.
|
||||||
|
# 0.5 moves the lens to focus on objects 2m away.
|
||||||
|
# 2 moves the lens to focus on objects 50cm away.
|
||||||
|
rpiCameraLensPosition: 0.0
|
||||||
|
# Autofocus window, in the form x,y,width,height where the coordinates
|
||||||
|
# are given as a proportion of the entire image.
|
||||||
|
rpiCameraAfWindow:
|
||||||
|
# Manual flicker correction period, in microseconds.
|
||||||
|
rpiCameraFlickerPeriod: 0
|
||||||
|
# Enables printing text on each frame.
|
||||||
|
rpiCameraTextOverlayEnable: false
|
||||||
|
# Text that is printed on each frame.
|
||||||
|
# format is the one of the strftime() function.
|
||||||
|
rpiCameraTextOverlay: '%Y-%m-%d %H:%M:%S - MediaMTX'
|
||||||
|
# Codec (auto, hardwareH264, softwareH264 or mjpeg).
|
||||||
|
# When is "auto" and stream is primary, it defaults to hardwareH264 (if available) or softwareH264.
|
||||||
|
# When is "auto" and stream is secondary, it defaults to mjpeg.
|
||||||
|
rpiCameraCodec: auto
|
||||||
|
# Period between IDR frames (when codec is hardwareH264 or softwareH264).
|
||||||
|
rpiCameraIDRPeriod: 60
|
||||||
|
# Bitrate (when codec is hardwareH264 or softwareH264).
|
||||||
|
rpiCameraBitrate: 5000000
|
||||||
|
# Hardware H264 profile (baseline, main or high) (when codec is hardwareH264).
|
||||||
|
rpiCameraHardwareH264Profile: main
|
||||||
|
# Hardware H264 level (4.0, 4.1 or 4.2) (when codec is hardwareH264).
|
||||||
|
rpiCameraHardwareH264Level: '4.1'
|
||||||
|
# Software H264 profile (baseline, main or high) (when codec is softwareH264).
|
||||||
|
rpiCameraSoftwareH264Profile: baseline
|
||||||
|
# Software H264 level (4.0, 4.1 or 4.2) (when codec is softwareH264).
|
||||||
|
rpiCameraSoftwareH264Level: '4.1'
|
||||||
|
# M-JPEG JPEG quality (when codec is mjpeg).
|
||||||
|
rpiCameraMJPEGQuality: 60
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
# Default path settings -> Hooks
|
||||||
|
|
||||||
|
# Command to run when this path is initialized.
|
||||||
|
# This can be used to publish a stream when the server is launched.
|
||||||
|
# This is terminated with SIGINT when the program closes.
|
||||||
|
# The following environment variables are available:
|
||||||
|
# * MTX_PATH: path name
|
||||||
|
# * RTSP_PORT: RTSP server port
|
||||||
|
# * G1, G2, ...: regular expression groups, if path name is
|
||||||
|
# a regular expression.
|
||||||
|
runOnInit:
|
||||||
|
# Restart the command if it exits.
|
||||||
|
runOnInitRestart: no
|
||||||
|
|
||||||
|
# Command to run when this path is requested by a reader
|
||||||
|
# and no one is publishing to this path yet.
|
||||||
|
# This can be used to publish a stream on demand.
|
||||||
|
# This is terminated with SIGINT when there are no readers anymore.
|
||||||
|
# The following environment variables are available:
|
||||||
|
# * MTX_PATH: path name
|
||||||
|
# * MTX_QUERY: query parameters (passed by first reader)
|
||||||
|
# * RTSP_PORT: RTSP server port
|
||||||
|
# * G1, G2, ...: regular expression groups, if path name is
|
||||||
|
# a regular expression.
|
||||||
|
runOnDemand:
|
||||||
|
# Restart the command if it exits.
|
||||||
|
runOnDemandRestart: no
|
||||||
|
# Readers will be put on hold until the runOnDemand command starts publishing
|
||||||
|
# or until this amount of time has passed.
|
||||||
|
runOnDemandStartTimeout: 10s
|
||||||
|
# The command will be closed when there are no
|
||||||
|
# readers connected and this amount of time has passed.
|
||||||
|
runOnDemandCloseAfter: 10s
|
||||||
|
# Command to run when there are no readers anymore.
|
||||||
|
# Environment variables are the same of runOnDemand.
|
||||||
|
runOnUnDemand:
|
||||||
|
|
||||||
|
# Command to run when the stream is ready to be read, whenever it is
|
||||||
|
# published by a client or pulled from a server / camera.
|
||||||
|
# This is terminated with SIGINT when the stream is not ready anymore.
|
||||||
|
# The following environment variables are available:
|
||||||
|
# * MTX_PATH: path name
|
||||||
|
# * MTX_QUERY: query parameters (passed by publisher)
|
||||||
|
# * MTX_SOURCE_TYPE: source type
|
||||||
|
# * MTX_SOURCE_ID: source ID
|
||||||
|
# * RTSP_PORT: RTSP server port
|
||||||
|
# * G1, G2, ...: regular expression groups, if path name is
|
||||||
|
# a regular expression.
|
||||||
|
runOnReady:
|
||||||
|
# Restart the command if it exits.
|
||||||
|
runOnReadyRestart: no
|
||||||
|
# Command to run when the stream is not available anymore.
|
||||||
|
# Environment variables are the same of runOnReady.
|
||||||
|
runOnNotReady:
|
||||||
|
|
||||||
|
# Command to run when a client starts reading.
|
||||||
|
# This is terminated with SIGINT when a client stops reading.
|
||||||
|
# The following environment variables are available:
|
||||||
|
# * MTX_PATH: path name
|
||||||
|
# * MTX_QUERY: query parameters (passed by reader)
|
||||||
|
# * MTX_READER_TYPE: reader type
|
||||||
|
# * MTX_READER_ID: reader ID
|
||||||
|
# * RTSP_PORT: RTSP server port
|
||||||
|
# * G1, G2, ...: regular expression groups, if path name is
|
||||||
|
# a regular expression.
|
||||||
|
runOnRead:
|
||||||
|
# Restart the command if it exits.
|
||||||
|
runOnReadRestart: no
|
||||||
|
# Command to run when a client stops reading.
|
||||||
|
# Environment variables are the same of runOnRead.
|
||||||
|
runOnUnread:
|
||||||
|
|
||||||
|
# Command to run when a recording segment is created.
|
||||||
|
# The following environment variables are available:
|
||||||
|
# * MTX_PATH: path name
|
||||||
|
# * MTX_SEGMENT_PATH: segment file path
|
||||||
|
# * RTSP_PORT: RTSP server port
|
||||||
|
# * G1, G2, ...: regular expression groups, if path name is
|
||||||
|
# a regular expression.
|
||||||
|
runOnRecordSegmentCreate:
|
||||||
|
|
||||||
|
# Command to run when a recording segment is complete.
|
||||||
|
# The following environment variables are available:
|
||||||
|
# * MTX_PATH: path name
|
||||||
|
# * MTX_SEGMENT_PATH: segment file path
|
||||||
|
# * MTX_SEGMENT_DURATION: segment duration
|
||||||
|
# * RTSP_PORT: RTSP server port
|
||||||
|
# * G1, G2, ...: regular expression groups, if path name is
|
||||||
|
# a regular expression.
|
||||||
|
runOnRecordSegmentComplete:
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
# Path settings
|
||||||
|
|
||||||
|
# Settings in "paths" are applied to specific paths, and the map key
|
||||||
|
# is the name of the path.
|
||||||
|
# Any setting in "pathDefaults" can be overridden here.
|
||||||
|
# It's possible to use regular expressions by using a tilde as prefix,
|
||||||
|
# for example "~^(test1|test2)$" will match both "test1" and "test2",
|
||||||
|
# for example "~^prefix" will match all paths that start with "prefix".
|
||||||
|
paths:
|
||||||
|
# example:
|
||||||
|
# my_camera:
|
||||||
|
# source: rtsp://my_camera
|
||||||
|
|
||||||
|
# Settings under path "all_others" are applied to all paths that
|
||||||
|
# do not match another entry.
|
||||||
|
all_others:
|
||||||
|
|
||||||
14
templates/mediamtx_service.service.j2
Normal file
14
templates/mediamtx_service.service.j2
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=MediaMTX Service
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
WorkingDirectory={{ mediamtx_working_folder }}
|
||||||
|
ExecStart={{ mediamtx_working_folder }}/mediamtx
|
||||||
|
Restart=always
|
||||||
|
User=root
|
||||||
|
Group=root
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
15
templates/service_control.service.j2
Normal file
15
templates/service_control.service.j2
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
[Unit]
|
||||||
|
Description={{ service_name }} Service Control API
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
User=root
|
||||||
|
Group=root
|
||||||
|
WorkingDirectory={{ service_control_folder }}
|
||||||
|
ExecStartPre=/bin/sleep 5
|
||||||
|
ExecStart={{ service_control_folder }}/venv/bin/python {{ service_control_folder }}/app.py
|
||||||
|
Restart=always
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
14
templates/stream_service.service.j2
Normal file
14
templates/stream_service.service.j2
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=VCR Streaming Service
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
WorkingDirectory={{ streaming_working_folder }}
|
||||||
|
ExecStart={{ streaming_working_folder }}/stream_service.sh
|
||||||
|
Restart=always
|
||||||
|
User=root
|
||||||
|
Group=root
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
12
templates/stream_service.sh.j2
Normal file
12
templates/stream_service.sh.j2
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
ffmpeg -hwaccel cuda -hwaccel_output_format cuda \
|
||||||
|
-f alsa -ac 2 -i {{ audio_device }} -thread_queue_size 64 \
|
||||||
|
-f v4l2 -framerate 30 -video_size 640x480 -input_format yuyv422 -i {{ video_device }} \
|
||||||
|
-c:v libx264 -preset ultrafast -tune zerolatency \
|
||||||
|
-vf "format=yuv420p" -g 60 -c:a aac -b:a 128k -ar 44100 \
|
||||||
|
-f flv rtmp://0.0.0.0:1935/stream
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user