Since long many of us have been searching, trying, helping & contributing in our efforts to stream & view live video from networked cameras. With minimum latency!
Many contributers have spent so many, many hours helping the rest of us; Like @kevinGodell, @BartButenaers and many others, not mentioned but never to be forgotten
So far, what I have experienced myself, we do have useful & working solutions even if they are a bit tricky to setup & configure, requiering several nodes of various kind and many times even advanced debugging
They all also tend to introduce some latency, i.e. the presented video is some seconds or more delayed relative the real life
Many tricks and tuning attempts have been tested to overcome. Sometimes sacrificing quality but even so, some delays are still noticable and present
The other day, for some unknown reason, I just stumbled over an interesting tool that looked promising; mediamtx
A fantastic light weight live media server!!
I installed it on a simple old RPi3 and configured it to connect to a TAPO netcam via RTSP. The server then provides the stream via various protocols and formats, the one I selected in this project is WebRTC since it is perfect for streaming with Ultra-Low Latency (Sub-500ms)
The CPU load on the RPi3 showed that it was not too much to worry about, only around 7%
Next on the table was to show the video and some controls in the FlowFuse Dashboard. This was not that difficult, just used the template node and added an iframe. See the image below
Another good thing is that you just consume one RTSP connection from camera (they normally have some limited numbers available) but the mediamtx can share the single stream with many users
In my case I have a number of locations where I have installed network (RTSP) cameras and a local RPi3. They are all members of my Tailscale network so I can reach them basically from ”anywhere”
If you feel you wanna try you can use the simple flow below as a starting example, just install the version of mediamtx that fits your environment, configure it and add one single template node in the flow!
[
{
"id": "622a7d44cf807c92",
"type": "ui-template",
"z": "3a2119e9c11365e2",
"group": "e09ee07d132abea6",
"page": "",
"ui": "",
"name": "",
"order": 0,
"width": 0,
"height": 0,
"head": "",
"format": "<template>\n <div>\n <!-- Rubrik med dynamisk statusindikator -->\n <div class=\"header-container\">\n <h2>Camera WebRTC stream: Tapo</h2>\n <span :class=\"['status-dot', isPlaying ? 'status-online' : 'status-offline']\"></span>\n </div>\n\n <!-- WebRTC Video via inbyggd iFrame -->\n <div class=\"video-container\">\n <iframe \n v-if=\"isPlaying\"\n src=\"http://ip-to-your-rpi:8889/tapo/\" \n scrolling=\"no\" \n allow=\"autoplay; encrypted-media; picture-in-picture\"\n allowfullscreen>\n </iframe>\n <!-- Meddelande när strömmen är stoppad -->\n <div v-else class=\"video-placeholder\">\n <p>Stream is stopped</p>\n </div>\n </div>\n\n <!-- Kontrollknappar -->\n <div class=\"button-container\">\n <v-btn color=\"success\" :disabled=\"isPlaying\" @click=\"startStream()\">Start</v-btn>\n <v-btn color=\"error\" :disabled=\"!isPlaying\" @click=\"stopStream()\">Stop</v-btn>\n </div>\n </div>\n</template>\n\n<script>\nexport default {\n data() {\n return {\n isPlaying: true\n }\n },\n methods: {\n startStream() {\n this.isPlaying = true;\n },\n stopStream() {\n this.isPlaying = false;\n }\n }\n}\n</script>\n\n<style>\n/* Layout för rubrik och indikator */\n.header-container {\n display: flex;\n align-items: center;\n gap: 10px;\n margin-bottom: 10px;\n}\n\n/* Grundstil för statuspricken */\n.status-dot {\n width: 12px;\n height: 12px;\n border-radius: 50%;\n display: inline-block;\n transition: background-color 0.3s ease;\n}\n\n/* Grön pulserande prick (Igång) */\n.status-online {\n background-color: #4caf50;\n box-shadow: 0 0 0 0 rgba(76, 175, 80, 0.7);\n animation: pulse 2s infinite;\n}\n\n/* Röd fast prick (Stoppad) */\n.status-offline {\n background-color: #f44336;\n box-shadow: none;\n animation: none;\n}\n\n/* Animation för pulserande effekt */\n@keyframes pulse {\n 0% {\n transform: scale(0.95);\n box-shadow: 0 0 0 0 rgba(76, 175, 80, 0.7);\n }\n 70% {\n transform: scale(1);\n box-shadow: 0 0 0 8px rgba(76, 175, 80, 0);\n }\n 100% {\n transform: scale(0.95);\n box-shadow: 0 0 0 0 rgba(76, 175, 80, 0);\n }\n}\n\n/* Video- och iframe-styling */\n.video-container {\n width: 100%;\n max-width: 640px;\n aspect-ratio: 16 / 9;\n margin-bottom: 15px;\n background: #000;\n border-radius: 8px;\n overflow: hidden;\n box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);\n position: relative;\n}\n\n.video-container iframe {\n width: 100%;\n height: 100%;\n border: none;\n display: block;\n}\n\n.video-placeholder {\n width: 100%;\n height: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n color: #aaa;\n font-size: 1.1em;\n font-family: sans-serif;\n}\n\n.button-container {\n display: flex;\n gap: 10px;\n margin-bottom: 20px;\n}\n</style>\n",
"storeOutMessages": true,
"passthru": true,
"resendOnRefresh": true,
"templateScope": "local",
"className": "",
"x": 710,
"y": 230,
"wires": [
[]
]
},
{
"id": "e09ee07d132abea6",
"type": "ui-group",
"name": "Group 4",
"page": "f85cf0eee9119058",
"width": "4",
"height": "1",
"order": 1,
"showTitle": false,
"className": "",
"visible": "true",
"disabled": "false",
"groupType": "default"
},
{
"id": "f85cf0eee9119058",
"type": "ui-page",
"name": "Home",
"ui": "72c1e5a9ec204878",
"path": "/home",
"icon": "home",
"layout": "grid",
"theme": "0d92c765bfad87e6",
"breakpoints": [
{
"name": "Default",
"px": "0",
"cols": "3"
},
{
"name": "Tablet",
"px": "576",
"cols": "6"
},
{
"name": "Small Desktop",
"px": "768",
"cols": "9"
},
{
"name": "Desktop",
"px": "1024",
"cols": "12"
}
],
"order": 2,
"className": "",
"visible": "true",
"disabled": "false"
},
{
"id": "72c1e5a9ec204878",
"type": "ui-base",
"name": "My Dashboard",
"path": "/dashboard",
"appIcon": "",
"includeClientData": true,
"acceptsClientConfig": [
"ui-notification",
"ui-control",
"ui-chart"
],
"showPathInSidebar": false,
"headerContent": "page",
"titleBarStyle": "default",
"showReconnectNotification": true,
"notificationDisplayTime": 5,
"showDisconnectNotification": true,
"allowInstall": true
},
{
"id": "0d92c765bfad87e6",
"type": "ui-theme",
"name": "Basic Blue Theme",
"colors": {
"surface": "#4d58ff",
"primary": "#0094ce",
"bgPage": "#eeeeee",
"groupBg": "#ffffff",
"groupOutline": "#cccccc"
},
"sizes": {
"pagePadding": "12px",
"groupGap": "12px",
"groupBorderRadius": "4px",
"widgetGap": "12px",
"density": "default"
}
},
{
"id": "893270d9dfd7315d",
"type": "global-config",
"env": [],
"modules": {
"@flowfuse/node-red-dashboard": "1.30.2"
}
}
]





