From 10299e547533184abd967fb39ce0d5f6970eb2ef Mon Sep 17 00:00:00 2001 From: Gui-Gos <97973228+Gui-Gos@users.noreply.github.com> Date: Tue, 6 Jan 2026 13:53:06 +0100 Subject: [PATCH] feat: Add MediaMTX streaming server app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add MediaMTX 1.15.6 - Modern, ready-to-use media streaming server Features: - Multi-protocol support: RTSP, RTMP, HLS, WebRTC, SRT - Zero configuration required to start - Automatic protocol conversion - Built-in recording capabilities (fMP4 format) - REST API for programmatic control - Prometheus metrics endpoint - Low resource consumption (Go-based) Configuration: - Main web UI port: 8889 (WebRTC) - RTSP: 8554, RTMP: 1935, HLS: 8888 - API: 9997, Metrics: 9998 - Optional authentication for API - Optional stream recording - Resource limits: 2 CPU / 2GB RAM (limits), 1 CPU / 512MB RAM (reservations) Volumes: - recordings: Persistent storage for recorded streams - config: Configuration files Use cases: - IP camera surveillance (RTSP/ONVIF) - Live streaming events - Broadcasting (OBS Studio, FFmpeg) - WebRTC video conferencing - IoT and drone video transmission Documentation: - Complete French documentation in description.md - Usage examples for OBS, FFmpeg, VLC - API reference and examples - Troubleshooting guide Note: logo.jpg needs to be added manually (see LOGO_README.txt) πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- apps/mediamtx/.DS_Store | Bin 0 -> 6148 bytes apps/mediamtx/config.json | 45 +++ apps/mediamtx/docker-compose.json | 142 +++++++ apps/mediamtx/metadata/description.md | 558 ++++++++++++++++++++++++++ apps/mediamtx/metadata/logo.jpg | Bin 0 -> 24073 bytes 5 files changed, 745 insertions(+) create mode 100644 apps/mediamtx/.DS_Store create mode 100644 apps/mediamtx/config.json create mode 100644 apps/mediamtx/docker-compose.json create mode 100644 apps/mediamtx/metadata/description.md create mode 100644 apps/mediamtx/metadata/logo.jpg diff --git a/apps/mediamtx/.DS_Store b/apps/mediamtx/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..cdd8148f40882859b6894609e638501f07ae11b7 GIT binary patch literal 6148 zcmeHKF>V4u473BqNogo4_Y3@Bg~$u?fj}-Dh$2aURo<1SWyW?9bfhB<8cW{U^?G)- zQ=HFc=G)J4W41E00Zz0p4s+u^eP$08aUdLLjJCHA+mG+B$65CE0J(#m?2WwN{KNTt zAzA?765%k+qn7|S z27tYAN<;+aNd+d=tHto7Bi<^n7fy*uH?QWy>t?SG#p8CI-y+?-Cu)=eQs7d7MJ`vY z|4;Bg`u|IkR#HF;+?4{p+&%BMcv98Y+2dGi8~g>%oNqV{^Ppgeatw@ej0MZ_I+8N4 ZagP07I3)%h@t_0sGeBKrQsBQ8_yK#26>0zg literal 0 HcmV?d00001 diff --git a/apps/mediamtx/config.json b/apps/mediamtx/config.json new file mode 100644 index 0000000..c61f657 --- /dev/null +++ b/apps/mediamtx/config.json @@ -0,0 +1,45 @@ +{ + "name": "MediaMTX", + "id": "mediamtx", + "available": true, + "exposable": true, + "dynamic_config": true, + "port": 8889, + "tipi_version": 2, + "version": "1.15.6", + "categories": ["media", "utilities"], + "short_desc": "Serveur de streaming multimΓ©dia moderne supportant RTSP, RTMP, HLS, WebRTC, SRT.", + "description": "MediaMTX est un serveur de streaming multimΓ©dia prΓͺt Γ  l'emploi et performant qui supporte RTSP, RTMP, HLS, WebRTC, SRT et plus encore. Zero configuration nΓ©cessaire pour dΓ©marrer.", + "author": "bluenviron", + "source": "https://github.com/bluenviron/mediamtx", + "website": "https://mediamtx.org", + "form_fields": [ + { + "type": "text", + "env_variable": "MTX_API_USERNAME", + "label": "API Username (optionnel)", + "default": "", + "required": false, + "hint": "Nom d'utilisateur pour l'API REST (laisser vide pour dΓ©sactiver l'authentification)" + }, + { + "type": "password", + "env_variable": "MTX_API_PASSWORD", + "label": "API Password (optionnel)", + "default": "", + "required": false, + "hint": "Mot de passe pour l'API REST" + }, + { + "type": "boolean", + "env_variable": "MTX_RECORD_ENABLED", + "label": "Activer l'enregistrement des streams", + "default": false, + "required": false + } + ], + "supported_architectures": [ + "amd64", + "arm64" + ] +} diff --git a/apps/mediamtx/docker-compose.json b/apps/mediamtx/docker-compose.json new file mode 100644 index 0000000..ce9814a --- /dev/null +++ b/apps/mediamtx/docker-compose.json @@ -0,0 +1,142 @@ +{ + "schemaVersion": 2, + "services": [ + { + "name": "mediamtx", + "image": "bluenviron/mediamtx:1.15.6", + "hostname": "mediamtx", + "isMain": true, + "internalPort": "8889", + "environment": [ + { + "key": "MTX_PROTOCOLS", + "value": "tcp" + }, + { + "key": "MTX_LOGLEVEL", + "value": "info" + }, + { + "key": "MTX_LOGDESTINATIONS", + "value": "stdout" + }, + { + "key": "MTX_RTSPADDRESS", + "value": ":8554" + }, + { + "key": "MTX_RTMPADDRESS", + "value": ":1935" + }, + { + "key": "MTX_HLSADDRESS", + "value": ":8888" + }, + { + "key": "MTX_WEBRTCADDRESS", + "value": ":8889" + }, + { + "key": "MTX_SRTADDRESS", + "value": ":8890" + }, + { + "key": "MTX_APIADDRESS", + "value": ":9997" + }, + { + "key": "MTX_METRICSADDRESS", + "value": ":9998" + }, + { + "key": "MTX_AUTHINTERNALUSERS", + "value": "${MTX_API_USERNAME:+${MTX_API_USERNAME}:${MTX_API_PASSWORD}}" + }, + { + "key": "MTX_PATHDEFAULTS_RECORD", + "value": "${MTX_RECORD_ENABLED:-false}" + }, + { + "key": "MTX_PATHDEFAULTS_RECORDPATH", + "value": "./recordings/%path/%Y-%m-%d_%H-%M-%S-%f" + }, + { + "key": "MTX_PATHDEFAULTS_RECORDFORMAT", + "value": "fmp4" + } + ], + "addPorts": [ + { + "containerPort": 8554, + "hostPort": 8554, + "tcp": true + }, + { + "containerPort": 8554, + "hostPort": 8554, + "udp": true + }, + { + "containerPort": 1935, + "hostPort": 1935, + "tcp": true + }, + { + "containerPort": 8888, + "hostPort": 8888, + "tcp": true + }, + { + "containerPort": 8890, + "hostPort": 8890, + "udp": true + }, + { + "containerPort": 8189, + "hostPort": 8189, + "udp": true + }, + { + "containerPort": 9997, + "hostPort": 9997, + "tcp": true + }, + { + "containerPort": 9998, + "hostPort": 9998, + "tcp": true + } + ], + "volumes": [ + { + "hostPath": "${APP_DATA_DIR}/data/recordings", + "containerPath": "/mediamtx/recordings" + }, + { + "hostPath": "${APP_DATA_DIR}/data/config", + "containerPath": "/mediamtx/config" + } + ], + "deploy": { + "resources": { + "limits": { + "cpus": "2.0", + "memory": "2G" + }, + "reservations": { + "cpus": "1.0", + "memory": "512M", + "devices": [] + } + } + }, + "healthCheck": { + "test": "wget --no-verbose --tries=1 --spider http://localhost:9997/v3/config/get || exit 1", + "interval": "30s", + "timeout": "10s", + "retries": 3, + "startPeriod": "10s" + } + } + ] +} diff --git a/apps/mediamtx/metadata/description.md b/apps/mediamtx/metadata/description.md new file mode 100644 index 0000000..f813f6d --- /dev/null +++ b/apps/mediamtx/metadata/description.md @@ -0,0 +1,558 @@ +# MediaMTX 1.15.6 pour Runtipi + +Serveur de streaming multimΓ©dia moderne, performant et prΓͺt Γ  l'emploi. + +**Version:** MediaMTX 1.15.6 +**Plateforme:** Runtipi +**Status:** βœ… Stable + +--- + +## πŸ“– Qu'est-ce que MediaMTX ? + +**MediaMTX** (anciennement rtsp-simple-server) est un serveur de streaming multimΓ©dia puissant et facile Γ  utiliser qui supporte de multiples protocoles: + +### Protocoles SupportΓ©s + +- **RTSP** - Real-Time Streaming Protocol +- **RTMP** - Real-Time Messaging Protocol +- **HLS** - HTTP Live Streaming +- **WebRTC** - Web Real-Time Communication +- **SRT** - Secure Reliable Transport +- **MPEG-TS** - MPEG Transport Stream +- **UDP/RTP** - User Datagram Protocol / Real-time Transport Protocol + +### CaractΓ©ristiques Principales + +- βœ… **Zero Configuration** - Fonctionne immΓ©diatement sans configuration +- βœ… **Conversion Automatique** - Conversion transparente entre tous les protocoles +- βœ… **Enregistrement IntΓ©grΓ©** - Sauvegarde des streams en MP4 ou MPEG-TS +- βœ… **Performance Optimale** - Γ‰crit en Go, faible consommation de ressources +- βœ… **API REST ComplΓ¨te** - ContrΓ΄le programmatique via HTTP +- βœ… **Metrics Prometheus** - Surveillance et monitoring intΓ©grΓ©s +- βœ… **Multi-plateforme** - Linux, Windows, macOS, ARM + +### Cas d'Usage + +- **Surveillance vidΓ©o** - CamΓ©ras IP (RTSP/ONVIF) +- **Streaming en direct** - Diffusion d'Γ©vΓ©nements, confΓ©rences +- **Broadcasting** - OBS Studio, FFmpeg, GStreamer +- **VisioconfΓ©rence** - WebRTC pour applications web +- **IoT et drones** - Transmission vidΓ©o temps rΓ©el +- **Enregistrement DVR** - Sauvegarde de flux vidΓ©o + +--- + +## πŸ—οΈ Architecture + +MediaMTX fonctionne comme un serveur central qui accepte des connexions entrantes (publishers) et redistribue les streams aux clients (viewers): + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Publishers β”‚ +β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ CamΓ©ra β”‚ β”‚ OBS β”‚ β”‚ FFmpeg β”‚ β”‚ Drone β”‚ β”‚ +β”‚ β”‚ IP β”‚ β”‚ Studio β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ (RTSP) β”‚ β”‚ (RTMP) β”‚ β”‚ (SRT) β”‚ β”‚ (WebRTC) β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ β”‚ β”‚ + β–Ό β–Ό β–Ό β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ MediaMTX Server β”‚ +β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Protocol Conversion & Stream Distribution β”‚ β”‚ +β”‚ β”‚ RTSP (8554) β”‚ RTMP (1935) β”‚ HLS (8888) β”‚ WebRTC β”‚ β”‚ +β”‚ β”‚ SRT (8890) β”‚ API (9997) β”‚ Metrics (9998) β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Enregistrement (optionnel) β”‚ β”‚ +β”‚ β”‚ Formats: fMP4, MPEG-TS β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ β”‚ β”‚ + β–Ό β–Ό β–Ό β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Viewers β”‚ +β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ VLC β”‚ β”‚Navigateurβ”‚ β”‚ Mobile β”‚ β”‚ Lecteur β”‚ β”‚ +β”‚ β”‚ (RTSP) β”‚ β”‚ (HLS) β”‚ β”‚ (WebRTC) β”‚ β”‚ (RTMP) β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +--- + +## πŸš€ Installation + +### PrΓ©requis + +- **Runtipi** installΓ© et fonctionnel +- **Ressources minimales:** + - **CPU:** 1 core (2+ recommandΓ©s) + - **RAM:** 512MB minimum (2GB recommandΓ©s pour production) + - **Disque:** 1GB + espace pour enregistrements (si activΓ©) +- **Ports rΓ©seau** accessibles (voir tableau des ports) + +### Installation via Runtipi + +1. **Ouvrir l'interface Runtipi** dans votre navigateur +2. **Aller dans "App Store"** +3. **Rechercher "MediaMTX"** +4. **Cliquer sur "Install"** +5. **Configurer les options** (optionnel): + - Authentification API (username/password) + - Activer l'enregistrement des streams +6. **Cliquer sur "Installer"** + +L'application dΓ©marre automatiquement et est prΓͺte Γ  recevoir des streams ! + +--- + +## πŸ” Configuration + +### Ports ExposΓ©s + +| Port | Protocole | Usage | AccΓ¨s | +|------|-----------|-------|-------| +| **8554** | TCP/UDP | RTSP | Public | +| **1935** | TCP | RTMP | Public | +| **8888** | TCP | HLS | Public | +| **8889** | TCP | WebRTC (Signaling) | Public | +| **8890** | UDP | WebRTC/SRT Media | Public | +| **8189** | UDP | WebRTC ICE | Public | +| **9997** | TCP | API REST | Interne/Admin | +| **9998** | TCP | Metrics Prometheus | Interne/Monitoring | + +### Variables d'Environnement + +Configurables via l'interface Runtipi: + +| Variable | Description | Valeur par dΓ©faut | +|----------|-------------|-------------------| +| `MTX_API_USERNAME` | Nom d'utilisateur API | *(vide - auth dΓ©sactivΓ©e)* | +| `MTX_API_PASSWORD` | Mot de passe API | *(vide)* | +| `MTX_RECORD_ENABLED` | Activer enregistrement | `false` | + +**ParamΓ¨tres Automatiques** (prΓ©-configurΓ©s): + +- `MTX_PROTOCOLS=tcp` - Protocoles Docker-friendly +- `MTX_LOGLEVEL=info` - Niveau de log +- `MTX_LOGDESTINATIONS=stdout` - Logs vers Docker +- Tous les ports configurΓ©s automatiquement + +### Authentification API + +Pour sΓ©curiser l'API REST: + +1. **Aller dans Runtipi β†’ Apps β†’ MediaMTX β†’ ParamΓ¨tres** +2. **DΓ©finir `MTX_API_USERNAME`** (ex: `admin`) +3. **DΓ©finir `MTX_API_PASSWORD`** (ex: mot de passe fort) +4. **RedΓ©marrer l'application** + +L'API nΓ©cessitera ensuite une authentification HTTP Basic: + +```bash +curl -u admin:PASSWORD http://localhost:9997/v3/config/get +``` + +### Enregistrement des Streams + +Pour activer l'enregistrement automatique: + +1. **Cocher "Activer l'enregistrement des streams"** lors de l'installation +2. Les enregistrements seront sauvegardΓ©s dans: + ``` + /opt/runtipi/app-data/*/mediamtx/data/recordings/ + ``` +3. **Format:** fMP4 (fragmentΓ© MP4, compatible avec tous les lecteurs) +4. **Nom des fichiers:** `{stream-name}/{date}_{heure}.mp4` + +**Exemple de structure:** +``` +recordings/ +β”œβ”€β”€ camera1/ +β”‚ β”œβ”€β”€ 2025-01-06_14-30-00-000.mp4 +β”‚ └── 2025-01-06_15-00-00-000.mp4 +└── stream2/ + └── 2025-01-06_14-45-00-000.mp4 +``` + +--- + +## πŸ“Ή Utilisation + +### 1. Publier un Stream (Publisher) + +#### Depuis OBS Studio (RTMP) + +1. **Ouvrir OBS Studio** +2. **ParamΓ¨tres β†’ Stream** +3. **Service:** PersonnalisΓ© +4. **Serveur:** `rtmp://VOTRE_IP:1935/` +5. **ClΓ© de stream:** `nom_du_stream` +6. **DΓ©marrer le streaming** + +#### Depuis FFmpeg (RTSP) + +```bash +ffmpeg -re -i video.mp4 -c copy -f rtsp rtsp://VOTRE_IP:8554/mon_stream +``` + +#### Depuis une CamΓ©ra IP (RTSP) + +Redirigez le stream de votre camΓ©ra: + +```bash +ffmpeg -i rtsp://camera_ip:554/stream \ + -c copy \ + -f rtsp rtsp://VOTRE_IP:8554/camera1 +``` + +#### Depuis FFmpeg (RTMP) + +```bash +ffmpeg -re -i video.mp4 -c copy -f flv rtmp://VOTRE_IP:1935/mon_stream +``` + +### 2. Lire un Stream (Viewer) + +#### Dans VLC (RTSP) + +1. **Ouvrir VLC** +2. **MΓ©dia β†’ Ouvrir un flux rΓ©seau** +3. **URL:** `rtsp://VOTRE_IP:8554/nom_du_stream` +4. **Lire** + +#### Dans un Navigateur (HLS) + +CrΓ©ez un fichier HTML: + +```html + + + + MediaMTX Stream + + + + + + + +``` + +#### Dans un Navigateur (WebRTC) + +```html + + + + MediaMTX WebRTC + + + + + + + +``` + +#### Avec FFplay + +```bash +ffplay rtsp://VOTRE_IP:8554/nom_du_stream +``` + +#### Avec curl (test rapide) + +```bash +curl http://VOTRE_IP:8888/nom_du_stream/index.m3u8 +``` + +--- + +## πŸ”§ API REST + +MediaMTX expose une API REST complΓ¨te sur le port **9997**. + +### Endpoints Principaux + +#### Lister les Streams Actifs + +```bash +curl http://localhost:9997/v3/paths/list +``` + +**RΓ©ponse:** +```json +{ + "itemCount": 2, + "pageCount": 1, + "items": [ + { + "name": "camera1", + "source": { + "type": "rtspSession", + "id": "abc123" + }, + "ready": true, + "tracks": ["H264", "AAC"], + "bytesReceived": 1234567, + "bytesSent": 7654321, + "readers": [ + { + "type": "hlsConn", + "id": "xyz789" + } + ] + } + ] +} +``` + +#### Obtenir la Configuration + +```bash +curl http://localhost:9997/v3/config/get +``` + +#### Modifier la Configuration + +```bash +curl -X POST http://localhost:9997/v3/config/set \ + -H "Content-Type: application/json" \ + -d '{"logLevel": "debug"}' +``` + +#### RedΓ©marrer le Serveur + +```bash +curl -X POST http://localhost:9997/v3/config/restart +``` + +#### Forcer la Fermeture d'un Stream + +```bash +curl -X POST http://localhost:9997/v3/paths/kick/camera1 +``` + +### Avec Authentification + +Si vous avez activΓ© l'authentification API: + +```bash +curl -u admin:PASSWORD http://localhost:9997/v3/paths/list +``` + +--- + +## πŸ“Š Monitoring avec Prometheus + +MediaMTX expose des mΓ©triques Prometheus sur le port **9998**. + +### AccΓ©der aux MΓ©triques + +```bash +curl http://localhost:9998/metrics +``` + +### MΓ©triques Disponibles + +``` +# Streams actifs +mediamtx_paths{state="ready"} 5 + +# DonnΓ©es reΓ§ues (bytes) +mediamtx_paths_bytes_received{name="camera1"} 1234567890 + +# DonnΓ©es envoyΓ©es (bytes) +mediamtx_paths_bytes_sent{name="camera1"} 9876543210 + +# Nombre de lecteurs par stream +mediamtx_paths_readers{name="camera1"} 3 +``` + +### Configuration Prometheus + +```yaml +scrape_configs: + - job_name: 'mediamtx' + static_configs: + - targets: ['localhost:9998'] +``` + +--- + +## 🎯 Exemples d'Utilisation + +### CamΓ©ra IP ONVIF/RTSP + +Relayer une camΓ©ra IP vers HLS pour lecture navigateur: + +```bash +# La camΓ©ra publie automatiquement sur RTSP +# MediaMTX rend disponible en HLS: +http://VOTRE_IP:8888/camera1/index.m3u8 +``` + +### Streaming depuis Webcam + +```bash +ffmpeg -f v4l2 -i /dev/video0 \ + -f alsa -i hw:0 \ + -c:v libx264 -preset ultrafast -tune zerolatency \ + -c:a aac \ + -f rtsp rtsp://localhost:8554/webcam +``` + +### Restreamer YouTube Live + +```bash +ffmpeg -i rtsp://VOTRE_IP:8554/mon_stream \ + -c copy \ + -f flv rtmp://a.rtmp.youtube.com/live2/VOTRE_CLE_STREAM +``` + +### Enregistrement PlanifiΓ© (Cron) + +```bash +# Enregistrer pendant 1 heure +ffmpeg -i rtsp://localhost:8554/camera1 \ + -c copy \ + -t 3600 \ + /recordings/$(date +%Y-%m-%d_%H-%M-%S).mp4 +``` + +### Stream Multi-Protocole + +Un seul stream source est automatiquement disponible sur tous les protocoles: + +- **RTSP:** `rtsp://VOTRE_IP:8554/stream1` +- **RTMP:** `rtmp://VOTRE_IP:1935/stream1` +- **HLS:** `http://VOTRE_IP:8888/stream1/index.m3u8` +- **WebRTC:** `http://VOTRE_IP:8889/stream1` + +--- + +## πŸ› DΓ©pannage + +### Le Stream ne s'Affiche Pas + +**VΓ©rifier que le stream est actif:** +```bash +curl http://localhost:9997/v3/paths/list +``` + +**VΓ©rifier les logs Docker:** +```bash +docker logs $(docker ps --filter "name=mediamtx" --format "{{.Names}}") +``` + +**Tester la connectivitΓ©:** +```bash +# RTSP +ffprobe rtsp://localhost:8554/nom_du_stream + +# HLS +curl http://localhost:8888/nom_du_stream/index.m3u8 +``` + +### ProblΓ¨mes WebRTC + +WebRTC nΓ©cessite une configuration NAT supplΓ©mentaire. Si vous Γͺtes derriΓ¨re un NAT/routeur: + +1. **Configurer le port forwarding** pour les ports UDP 8890, 8189 +2. **Ajouter votre IP publique** dans la configuration + +### Latence Γ‰levΓ©e + +Pour rΓ©duire la latence: + +- **Utiliser RTSP** avec UDP (latence ~200ms) +- **Utiliser WebRTC** (latence <100ms) +- Γ‰viter HLS (latence 6-30 secondes) + +### Performances DΓ©gradΓ©es + +**VΓ©rifier l'utilisation des ressources:** +```bash +docker stats $(docker ps --filter "name=mediamtx" --format "{{.Names}}") +``` + +**Augmenter les ressources** si nΓ©cessaire dans Runtipi β†’ Apps β†’ MediaMTX β†’ ParamΓ¨tres. + +--- + +## πŸ“š Ressources + +### Documentation Officielle + +- **Site Web:** https://mediamtx.org +- **Documentation:** https://mediamtx.org/docs/ +- **GitHub:** https://github.com/bluenviron/mediamtx +- **Discord Community:** https://discord.gg/MmAErXH + +### Outils Compatibles + +**Publishers:** +- OBS Studio +- FFmpeg +- GStreamer +- v4l2rtspserver +- CamΓ©ras IP (ONVIF/RTSP) +- Drones (Tello, DJI) + +**Viewers:** +- VLC Media Player +- FFplay +- Navigateurs web (HLS/WebRTC) +- Applications mobiles (RTSP/HLS) +- Appareils IPTV + +--- + +## πŸ“„ Licence + +**MediaMTX** est un logiciel open-source sous licence **MIT**. + +- **DΓ©veloppeur:** bluenviron (formerly Aler9) +- **Version:** 1.15.6 +- **DerniΓ¨re mise Γ  jour:** 28 dΓ©cembre 2024 + +Cette application Runtipi utilise l'image Docker officielle de MediaMTX. + +--- + +**πŸŽ‰ Votre serveur de streaming MediaMTX est maintenant prΓͺt !** + +Commencez Γ  publier et lire des streams en quelques commandes. diff --git a/apps/mediamtx/metadata/logo.jpg b/apps/mediamtx/metadata/logo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..aef8ff3bd594247976553fe372218c4c2c1f3c5b GIT binary patch literal 24073 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