[beta testing] nodes for live streaming mp4

You can even set the ffmpeg path as environment variable and just leave ffmpeg as >_Path

which browser? which video playback type (socket. io, hls.js)?

The spinning loading circle and control buttons on the video element are created by your browser. I simply give a video source to the video element. I think I could add an option to control visibility of controls which will add the option to the html. I just ran some quick tests and it seems like autoplay still works when the controls are hidden, atleast on desktop browsers. Some mobile browsers may not like this. didnt test yet. The spinning loading circle gets hidden along when the controls are hidden.

I get what you mean about the pause button. It is pretty useless when dealing with live video. Pausing causes the live video to buffer and then pressing play will cause the video the lag behind. It should really be a stop/start button. The good news is that you can make this happen. If you capture the output from mp4frag that gives the playlist info to ui_mp4frag, then you can wire a load/unload button on your ui. The unload should send a payload with value '' (empty string) which will cause the video player source to unload in ui_mp4frag. Then have your load button send the captured playlist to the ui_mp4frag node to start the video source again.

Do you mean adding ffmpeg to the system path or in the settings.js file?

Thanks Kevin.

  • I made a quick video to show you more. Some times (but really just a few) it happens even within the ui_mp4frag, but it practically always happens when I use http window externally to node-red.
    It seems happening only on chrome. I tried it with edge and firefox and no loading circle is shown. And only streaming IP camera video. Trying with some internet links it doesn't seem to happen.
    As you can see for the video playback type I'm using mp4 by converting my rtsp IP camera. By the way do you think hls would be better? There is a way to convert rtsp in hls? I don't understand because as payload I even see hlsPlaylist: "/mp4frag/test52/hls.m3u8" coming out from the first output of the mp4frag node. But I'm not able to use it in an external http window.
  • For the pause button, great, I made it works as you suggested by load/unload button. Since, as I said, I even need to use the streaming in an external http window, outside to node-red like you can see from the video, do you think there could be some possibility to achieve the same result? Mybe by some control within mp4frag node?
  • Last thing. For the ffmpeg I mean as system path, not in the settings.js file. I'm using this solution on windows 10 without problem.

You may test this little setup. I have made a simplified flow using the ui_template node instead of the ui_mp4frags. The flow uses the m3u8 playlist and works with chrome, edge, most likely ff and others and also has automatic detection of Safari so it works there as well

You have to change the ffmpeg command to fit your camera since what is in is for one of mine


  1. Open the browser, navigate to the dashboard "Test" page
  2. Start the stream
  3. Click inject to start viewing

I do not have any circles in any browser. If you still see circles you are not feeding the view with segments fast enough


[{"id":"c7b19db2.4ab3b","type":"ffmpeg-spawn","z":"fcecd26b.a4283","name":"237","outputs":5,"migrate":2e-9,"cmdPath":"ffmpeg","cmdArgs":"[\"-i\",\"\",\"-c:v\",\"h264_omx\",\"-vb\",\"20M\",\"-c:a\",\"aac\",\"-f\",\"mp4\",\"-movflags\",\"+frag_every_frame+empty_moov+default_base_moof\",\"-min_frag_duration\",\"500000\",\"pipe:1\"]","cmdOutputs":4,"killSignal":"SIGTERM","x":430,"y":180,"wires":[["74bd283f.7aa378"],["74bd283f.7aa378"],[],[],[]]},{"id":"ac7b3012.a103c","type":"inject","z":"fcecd26b.a4283","name":"stop default","props":[{"p":"action","v":"{\"command\":\"stop\"}","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payloadType":"str","x":210,"y":200,"wires":[["c7b19db2.4ab3b"]]},{"id":"74bd283f.7aa378","type":"mp4frag","z":"fcecd26b.a4283","name":"","migrate":2e-9,"hlsPlaylistSize":"10","hlsPlaylistExtra":"5","basePath":"237","repeated":"true","timeLimit":"600000","preBuffer":"3","x":460,"y":260,"wires":[[],[]]},{"id":"b0088ad1.edf478","type":"inject","z":"fcecd26b.a4283","name":"start default","props":[{"p":"action","v":"{\"command\":\"start\"}","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":"1","topic":"","payloadType":"str","x":210,"y":160,"wires":[["c7b19db2.4ab3b"]]},{"id":"7e9e3974.d95598","type":"comment","z":"fcecd26b.a4283","name":"Video sources","info":"","x":210,"y":120,"wires":[]},{"id":"ef57bbef.7699f8","type":"ui_template","z":"fcecd26b.a4283","group":"16030ced.45add3","name":"Main Display","order":5,"width":12,"height":8,"format":"<script src=\"//cdn.jsdelivr.net/npm/hls.js@latest\"></script>\n\n<script type=\"text/javascript\">\nhls_m = undefined;\n\n(function(scope) {\n    scope.$watch('msg', function(msg) {\n\n        if (msg.url.match('.m3u8')) {\n            if(hls_m!=undefined){hls_m.destroy();}\n            $(\"#hls_\"+scope.$id).hide();\n            $(\"#saf_\"+scope.$id).hide();\n            if(isSafari()){\n                $(\"#saf_\"+scope.$id).show();\n            }\n            if(!isSafari()){\n                $(\"#hls_\"+scope.$id).show();\n                hls_m = new Hls();\n                if (Hls.isSupported()) {\n                    let z = $(\"#hls_\"+scope.$id)[0];\n                    // bind them together\n                    hls_m.attachMedia(z);\n                    hls_m.on(Hls.Events.MEDIA_ATTACHED, function () {\n                      hls_m.loadSource(msg.url);\n                      hls_m.on(Hls.Events.MANIFEST_PARSED, function () {\n                        z.play();  \n                      });\n                    });\n                }\n            }\n        }\n    });\n})(scope);\n\nfunction isSafari() {\n    if (/apple/i.test(navigator.vendor)) {\n        //alert(\"Safari\");\n        return true;\n    }else{\n        return false;\n    } \n}\n\nthis.scope.action = function(event) { return event; }\n\nthis.scope.send({payload:'do_init'});\n\n</script>\n\n<div>\n<center>\n    <table>\n        <tr>\n    \t\t<td style=\"text-align: center\">\n    \t\t<video autoplay muted controls id=\"{{'hls_'+$id}}\" width='320px' height='240px' type=\"video/mp4\"></video>\n    \t\t</td>\n        </tr>\n    </table>\n</center>\n</div>\n\n<div id=\"{{'saf_'+$id}}\">\n<center>\n    <table>\n        <tr>\n    \t\t<td style=\"text-align: center\">\n    \t\t<video autoplay muted controls src={{msg.url}} width='320px' height='240px' type=\"video/mp4\"></video>\n    \t\t</td>\n        </tr>\n    </table>\n</center>\n</div>\n\n","storeOutMessages":false,"fwdInMessages":false,"resendOnRefresh":false,"templateScope":"local","x":450,"y":340,"wires":[[]]},{"id":"8e20a69a.9841b8","type":"inject","z":"fcecd26b.a4283","name":"","props":[{"p":"url","v":"/mp4frag/237/hls.m3u8","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":190,"y":340,"wires":[["ef57bbef.7699f8"]]},{"id":"16030ced.45add3","type":"ui_group","name":"Test","tab":"aed64a14.9adfe8","order":1,"disp":true,"width":"14","collapse":false},{"id":"aed64a14.9adfe8","type":"ui_tab","name":"Test","icon":"dashboard","disabled":false,"hidden":false}]
1 Like

Can you take a screenshot of your video settings of ui_mp4frag?

Yes, that is normal. You are viewing the video source directly in the browser, which means you have no control over how it is displayed. That would require a browser level fix, which is way beyond the scope of what we are doing. And fyi, the video is endless, so it can never be fully loaded. Chrome must consider that to always be in a state of "loading."

The other issue that could cause the loading circles would be the delivery of the video. Is the equipment overloaded, is the cpu running high, is the network switch starting to fault? There could be many reasons.

If I knew that you were using the socket .io player (waiting for the screenshot of your settings), then I could say it is because I push the video to keep it as close to realtime as possible, which will result in the occasional pausing when the time is adjusted. If you need a smoother video playback, with the drawback of being a little further from realtime, then you should use hls.js. Their lib is 26000 lines of code vs my 1000 lines. They can definitely do more for a better playback experience.

The only browser that will support playing hls.m3u8 directly would be safari. That is their preferred video streaming type. If you need to play that in other browsers, then it would need something such as hls.js. You could also give that url to vlc or any other app capable of HLS playback.

Do you mean ui_mp4frag? Here is another idea to test. If you have "unload" and "threshold" settings configured for ui_mp4frag, then the video player should automatically unload the video source when the video player is obstructed, scrolled out of view, etc. You can set up some type of button to interact with the video player to cover it up with some other element, such as a jpeg, or maybe alter its appearance, or collapsing the ui group that contains it (Allow group to be collapsed). The video player element id will always be based on the node id, so you can reliably target the element as needed.

I was hoping you were going to jump in and share that. Thanks.

I would also like to see the settings of node-red-contrib-mp4frag.

You might not have enough segments available when delivering to the browser from the server. The "extra" can help when the browser client is slow to request segments that might already be off the hls.m3u8 playlist.

Thanks a lot guys. Probably mainly it depends from other issue like cpu running high, network, etc as you suggested. Now it's working better. I even test the @krambriw suggested setup and everything seems ok now. Last day I tryed with several setup, restarting PC and so on, but always the same!

Here are the setup.

and mp4frag

No I intended exactly the mp4frag, not the ui. Since it is the one that creates the front end that I can use externally to node-red

I guess it is a matter of language. I would describe node-red-contrib-mp4frag as the backend code. It simply consumes pieces of mp4 video given to it by ffmpeg and then makes it available via http or socket. io for consumption by front end code such as node-red-contrib-ui-mp4frag or your own custom video player.

Either way, I am working on a solution that will allow you to make a set of custom controls using a ui-template so that you can control the video player on the client side in the node-red-dashboard. I will pass the videoId in the output of node-red-contrib-ui-mp4frag so that you can receive it in a template node and then assign some event listeners to your buttons to control many aspects of the video element.

A rough idea of what I mean for using external controls:

rough draft template node (currently will not work until i push update of node-red-contrib-ui-mp4frag)

edit: deleted garbage flow

1 Like

Thanks a lot for all the clarification.
Last thing. Within node-red I use two IP camera, of course with their own mp4frag and ui_mp4frag. The ui_mp4frag are on the same tab. Only on the android mobiles phone the first ui_mp4frag show both the cameras' imagines, continuously switching between their video

Unfortunately, that could be a limitation of the android device. I have no way of testing that, although I do remember seeing that happen years ago in some older device or browser. Can't remember for sure because I have not experienced that in my 28 (16 main + 12 sub) live video streams used on all of my devices. You may want to try a different streaming type by changing the players order. But realistically, a mobile device should never play more than 1 hd stream at a time, although we try to do it anyways. You will notice many video streaming sites such as youtube never have more than 1 video playing at any given time.

new basic flow to show custom video controls:
edited aug 14

[{"id":"8ab36938.5af828","type":"tab","label":"red bull live","disabled":false,"info":""},{"id":"bdb825ab.ff93d8","type":"ui_template","z":"8ab36938.5af828","group":"5c433dd6.328804","name":".ui-mp4frag-video","order":2,"width":0,"height":0,"format":"<style>\n  .ui-mp4frag-video {\n    pointer-events: none;\n  }\n</style>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"global","x":130,"y":220,"wires":[[]]},{"id":"7016608a.d2e2b","type":"ui_mp4frag","z":"8ab36938.5af828","name":"","group":"66bd4f86.9cd83","order":1,"width":"0","height":"0","readyPoster":"https://wallpapercave.com/wp/oR4lJ5R.jpg","errorPoster":"https://www.midnightriders.com/wp-content/uploads/2012/04/Red-Bull-Under-the-Wheels.jpg","hlsJsConfig":"{\"liveDurationInfinity\":true,\"liveBackBufferLength\":5,\"maxBufferLength\":10,\"manifestLoadingTimeOut\":1000,\"manifestLoadingMaxRetry\":10,\"manifestLoadingRetryDelay\":500}","autoplay":"true","unload":"true","threshold":0.1,"controls":"false","muted":"true","players":["socket.io","hls.js","hls","mp4"],"x":830,"y":100,"wires":[["377e7f11.2387a"]]},{"id":"377e7f11.2387a","type":"ui_template","z":"8ab36938.5af828","group":"66bd4f86.9cd83","name":"video controls","order":2,"width":0,"height":0,"format":"<div layout-fill layout=\"row\">\n\n    <div layout-fill>\n        <md-button layout-fill class=\"md-raised\" ng-click=\"toggleControls()\">\n            <md-icon class=\"material-icons\">smart_button</md-icon>\n        </md-button>\n    </div>\n        \n    <div layout-fill>\n        <md-button layout-fill class=\"md-raised\" ng-click=\"playPause()\">\n            <md-icon class=\"material-icons\">play_arrow</md-icon>\n        </md-button>\n    </div>\n    \n    <div layout-fill>\n        <md-button layout-fill class=\"md-raised\" ng-click=\"videoFullscreen()\">\n            <md-icon class=\"material-icons\">fullscreen</md-icon>\n        </md-button>\n    </div>\n\n    <div layout-fill>\n        <md-button layout-fill class=\"md-raised\" ng-click=\"toggleMuted()\">\n            <md-icon class=\"material-icons\">volume_mute</md-icon>\n        </md-button>\n    </div>\n    \n    <div layout-fill>\n        <md-button layout-fill class=\"md-raised\" ng-click=\"videoDisplay()\">\n            <md-icon class=\"material-icons\">visibility</md-icon>\n        </md-button>\n    </div>\n\n</div>\n\n<script>\n\n((scope) => {\n\n    const unwatchMsg = scope.$watch('msg', (msg) => {\n\n        if (msg && msg.videoId) {\n\n            const video = document.getElementById(msg.videoId);\n            \n            if (video) {\n\n                const requestFullscreen = video.webkitRequestFullscreen || video.webkitEnterFullScreen || video.mozRequestFullScreen || video.requestFullscreen;\n\n                scope.toggleControls = () => {\n\n                    video.controls = !video.controls;\n\n                }\n\n                scope.playPause = () => {\n\n                    video.paused ? video.play() : video.pause();\n\n                }\n\n                scope.toggleMuted = () => {\n\n                    video.muted = !video.muted;\n\n                }\n\n                scope.videoFullscreen = () => {\n\n                    requestFullscreen.call(video);\n\n                }\n\n                scope.videoDisplay = () => {\n\n                    video.style.display = video.style.display === 'none' ? 'initial' : 'none';\n\n                }\n\n                unwatchMsg();\n\n            }\n\n        }\n\n    });\n\n})(scope);\n</script>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","x":1060,"y":100,"wires":[[]]},{"id":"9820374f.193008","type":"inject","z":"8ab36938.5af828","name":"start","props":[{"p":"action","v":"{\"command\":\"start\"}","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":110,"y":60,"wires":[["abc3dd53.cf64f"]]},{"id":"abc3dd53.cf64f","type":"ffmpeg-spawn","z":"8ab36938.5af828","name":"","outputs":2,"migrate":2e-9,"cmdPath":"ffmpeg","cmdArgs":"[\"-re\",\"-i\",\"http://rbmn-live.akamaized.net/hls/live/590964/BoRB-AT/master_1660.m3u8\",\"-c:a\",\"aac\",\"-c:v\",\"copy\",\"-f\",\"mp4\",\"-movflags\",\"+frag_keyframe+empty_moov+default_base_moof\",\"-metadata\",\"title=red bull live\",\"pipe:1\"]","cmdOutputs":1,"killSignal":"SIGTERM","x":320,"y":100,"wires":[["fa72c593.27f9d8"],["fa72c593.27f9d8"]]},{"id":"fa72c593.27f9d8","type":"mp4frag","z":"8ab36938.5af828","name":"","migrate":2e-9,"hlsPlaylistSize":"10","hlsPlaylistExtra":"5","basePath":"red_bull_live","repeated":"false","timeLimit":100000,"preBuffer":1,"x":580,"y":100,"wires":[["7016608a.d2e2b"],[]]},{"id":"cf45c33e.1aa07","type":"inject","z":"8ab36938.5af828","name":"stop","props":[{"p":"action","v":"{\"command\":\"stop\"}","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":110,"y":140,"wires":[["abc3dd53.cf64f"]]},{"id":"f0aa7e0f.86fcf","type":"ui_template","z":"8ab36938.5af828","group":"5c433dd6.328804","name":".nr-dashboard-ui_mp4frag + .nr-dashboard-template","order":4,"width":0,"height":0,"format":"<style>\n  .nr-dashboard-ui_mp4frag + .nr-dashboard-template {\n    padding: 0;\n  }\n</style>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"global","x":240,"y":280,"wires":[[]]},{"id":"5c433dd6.328804","type":"ui_group","z":"","name":"Default","tab":"eeb0f4e7.54d118","order":1,"disp":true,"width":"6","collapse":false},{"id":"66bd4f86.9cd83","type":"ui_group","z":"","name":"red bull live","tab":"6183454e.54b2dc","order":1,"disp":true,"width":"12","collapse":true},{"id":"eeb0f4e7.54d118","type":"ui_tab","z":"","d":true,"name":"test","icon":"dashboard","disabled":false,"hidden":false},{"id":"6183454e.54b2dc","type":"ui_tab","z":"","name":"red bull live","icon":"dashboard","disabled":false,"hidden":false}]

The video controls are not limited to my example. You can look up the video element to see what other apis are exposed, such as changing volume, etc.

also, notice that ui-mp4frag-video class css that causes the video player to ignore your accidental clicking.

p.s. I would appreciate any recommendations to improve the ui template custom video controls. I struggle with trying to piece together angular stuff, but it seems to work.

forgot to mention, you will need to get the latest version of ui_mp4frag from github.

1 Like

If I were you, I would start a separate discussion about that. Just to make sure to get the attention from e.g. the styling guys...

Thanks for those amazing nodes. Showing the live streams of my cctv camera's in NR dashboard was a long awaited wish. So far so good.
It would also be nice if I can watch one of the streams on my LG smart tv. Preferable in the webbrowser. This because I can control my LG tv with NR and want to show the image from the frontdoor automatic when somebody rings the bell.
My LG tv is running on WebOs and has a unbranded webbrowser.
I tried playing the stream with the link or Both will give an empty mediaplayer in the browser.

I understand that you can't support every browser out there, but maybe some tip or tricks to get it maybe working.

Thanks so much @Kevin

If you are accessing the urls to the video sources directly in the browser, then you will be faced with some limitations. The video.mp4 is a live stream and it cannot be played directly in a browser that only makes byte range requests, such as safari (and maybe your lg browser). Accessing the hls.m3u8 directly also has limited support, with safari being one browser that supports it. So, your best bet would be to see if your browser supports media source extensions and instead of accessing the video directly, make a small html page that loads only the bare minimum to play it, such as hls.js. Visit this page http://html5test.com/ in the lg browser and pay close attention to the video and streaming sections. It should report back something like this:

I hope that means it is working for you.

I made some flows that you can try. Instead of directly viewing the video source, wrap it in a small amount of html and load that web page to your smart tv. The css is meant to cause the video to fill the entire screen. Of course, make any edits to suit yours needs. If you ultimately find that hls.js works best for you, then I would suggest going to their site to see all of the available settings that you can tweak to customize or improve playback.

[{"id":"df718963.3e14b8","type":"http in","z":"5898e05d.bf0e7","name":"","url":"/mp4/:id","method":"get","upload":false,"swaggerDoc":"","x":170,"y":560,"wires":[["be5d0ef.0f4dcf"]]},{"id":"be5d0ef.0f4dcf","type":"template","z":"5898e05d.bf0e7","name":"","field":"payload","fieldType":"msg","format":"html","syntax":"mustache","template":"<!doctype html>\n<html>\n    <head>\n        <title>{{req.params.id}}</title>\n        <style>\n            html {\n                height: 100%;\n                overflow: hidden;\n            }\n            body {\n                height: 100%;\n                margin: 0;\n            }\n            video {\n                width: 100%;\n                height: 100%;\n                object-fit: fill;\n            }\n        </style>\n    </head>\n    <body>\n        <video preload=\"auto\" controls autoplay muted playsinline>\n            <source src=\"/mp4frag/{{req.params.id}}/video.mp4\" type=\"video/mp4\"/>\n        </video>\n    </body>\n</html>","output":"str","x":380,"y":560,"wires":[["fdc06252.aae2c"]]},{"id":"fdc06252.aae2c","type":"http response","z":"5898e05d.bf0e7","name":"","x":570,"y":560,"wires":[]},{"id":"a8dd6f4f.d533e","type":"http in","z":"5898e05d.bf0e7","name":"","url":"/hlsjs/:id","method":"get","upload":false,"swaggerDoc":"","x":170,"y":600,"wires":[["e133947f.004cd8"]]},{"id":"e133947f.004cd8","type":"template","z":"5898e05d.bf0e7","name":"","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"<!doctype html>\n<html>\n    <head>\n        <title>{{req.params.id}}</title>\n        <style>\n            html {\n                height: 100%;\n                overflow: hidden;\n            }\n            body {\n                height: 100%;\n                margin: 0;\n            }\n            video {\n                width: 100%;\n                height: 100%;\n                object-fit: fill;\n            }\n        </style>\n        <script src=\"//cdn.jsdelivr.net/npm/hls.js@latest\"></script>\n    </head>\n    <body>\n        <video id=\"video\" preload=\"auto\" controls autoplay muted playsinline></video>\n        <script>\n            const video = document.getElementById('video');\n            const videoSrc = '/mp4frag/{{req.params.id}}/hls.m3u8';\n            if (Hls.isSupported()) {\n                const hls = new Hls();\n                hls.loadSource(videoSrc);\n                hls.attachMedia(video);\n            } else if (video.canPlayType('application/vnd.apple.mpegurl')) {\n                video.src = videoSrc;\n            } else {\n                alert('video is not supported');\n            }\n        </script>\n    </body>\n</html>","output":"str","x":380,"y":600,"wires":[["6f169a4e.8174c4"]]},{"id":"6f169a4e.8174c4","type":"http response","z":"5898e05d.bf0e7","name":"","x":570,"y":600,"wires":[]},{"id":"3b34dc4f.acf604","type":"http in","z":"5898e05d.bf0e7","name":"","url":"/fallback/:id","method":"get","upload":false,"swaggerDoc":"","x":180,"y":480,"wires":[["ed1c7ffd.22006"]]},{"id":"ed1c7ffd.22006","type":"template","z":"5898e05d.bf0e7","name":"","field":"payload","fieldType":"msg","format":"html","syntax":"mustache","template":"<!doctype html>\n<html>\n    <head>\n        <title>{{req.params.id}}</title>\n        <style>\n            html {\n                height: 100%;\n                overflow: hidden;\n            }\n            body {\n                height: 100%;\n                margin: 0;\n            }\n            video {\n                width: 100%;\n                height: 100%;\n                object-fit: fill;\n            }\n        </style>\n    </head>\n    <body>\n        <video preload=\"auto\" controls autoplay muted playsinline>\n            <source src=\"/mp4frag/{{req.params.id}}/hls.m3u8\" type=\"application/x-mpegURL\"/>\n            <source src=\"/mp4frag/{{req.params.id}}/video.mp4\" type=\"video/mp4\"/>\n        </video>\n    </body>\n</html>","output":"str","x":380,"y":480,"wires":[["7f9afb53.e4cc94"]]},{"id":"7f9afb53.e4cc94","type":"http response","z":"5898e05d.bf0e7","name":"","x":570,"y":480,"wires":[]},{"id":"e4e3a1ca.ac835","type":"http in","z":"5898e05d.bf0e7","name":"","url":"/hls/:id","method":"get","upload":false,"swaggerDoc":"","x":170,"y":520,"wires":[["d53a2f40.4283f"]]},{"id":"d53a2f40.4283f","type":"template","z":"5898e05d.bf0e7","name":"","field":"payload","fieldType":"msg","format":"html","syntax":"mustache","template":"<!doctype html>\n<html>\n    <head>\n        <title>{{req.params.id}}</title>\n        <style>\n            html {\n                height: 100%;\n                overflow: hidden;\n            }\n            body {\n                height: 100%;\n                margin: 0;\n            }\n            video {\n                width: 100%;\n                height: 100%;\n                object-fit: fill;\n            }\n        </style>\n    </head>\n    <body>\n        <video preload=\"auto\" controls autoplay muted playsinline>\n            <source src=\"/mp4frag/{{req.params.id}}/hls.m3u8\" type=\"application/x-mpegURL\"/>\n        </video>\n    </body>\n</html>","output":"str","x":380,"y":520,"wires":[["bb026b15.fcf818"]]},{"id":"bb026b15.fcf818","type":"http response","z":"5898e05d.bf0e7","name":"","x":570,"y":520,"wires":[]}]

To access the 3 types of video playback (mp4, native HLS, & hls.js), you would go to,,

The url with fallback illustrates using 2 video sources, which will allow the browser to pick the 1st one that it finds to be compatible.

1 Like

Hi Kevin, thanks for all the effort you putt in all your reply 's.
To start with the good news, the third url with hlsjs works! Thanks again.
The other 2 will give a empty media player in the browser.

If you are still interested here is the result from Visit this page http://html5test.com/ with the LG browser.

To get the url correct to the LG tv with the help of node-red-contrib-lgtv you need the escape the slashes in the url.
Nice, now we can see who's at the front door when the bell is ringed or at the back gate when the motion detection is trigged.
Sample, LG tv will start the browser and opens the cam url.

[{"id":"6df1cf3c734966ca","type":"inject","z":"44908087.da8e9","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payloadType":"date","x":480,"y":2200,"wires":[["7f41346eb1b5345a"]]},{"id":"7f41346eb1b5345a","type":"function","z":"44908087.da8e9","name":"","func":"msg.payload  = \"http:\\/\\/\\/hlsjs\\/mycam\"\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":700,"y":2200,"wires":[["1da5d5d8dd44338b"]]},{"id":"1da5d5d8dd44338b","type":"lgtv-browser","z":"44908087.da8e9","tv":"b3c0aa8f.8d58c8","name":"Cam Achter","x":910,"y":2200,"wires":[]},{"id":"b3c0aa8f.8d58c8","type":"lgtv-config","host":""}]
1 Like

Kevin, have you thought of optionally allowing the incoming msg to pass in the 'basepath'? It could be useful if someone wanted to put ffmpeg-spawn in a subflow.

I've got a case in mind of having multiple cameras that may or may not need to use mp4frag. Currently, since the 'basepath' is hardcoded, there needs to be multiple copies of the code processing it. With the ability to pass the value in a msg (msg.basepath) we could create one flow, and then make it a subflow cutting down maintenance.

For example: if you have 10 cameras, you could fix the issueonce in the subflow instead of having to fix it in 10 locations.

Isn't the basePath property from node-red-contrib-mp4frag? I am not 100% sure what you mean, perhaps because I have not finished my morning coffee. Is there any way that you can post a screenshot or some additional details so that I can get a better understanding?

1 Like

Oops, I moved my question from the ffmpeg thread to here.

Yes, it is the mp4frag node we have in mind. We would like to be able to inject it to the mp4frag node via incoming msg.basePath

Like you have written in the readme doc
hls playlist path: /mp4frag/ {basePath}/hls.m3u8

A simple flow to demonstrate


It's doable, but would require quite a bit of restructuring of the code. Currently, the http routes are created when the node is instantiated and removed when closed. If we are constantly adding and removing routes during runtime, I wonder about any possible negative side effects to node-red and any other nodes that also add routes to httpNode.

Can you give more details of expectations, such as:
Should the buffered segments survive the changing of the basePath or should that cause the entire buffer to be cleared? What should happen internally if you try to pass mp4 buffer from ffmpeg to mp4frag before giving it a basePath value? Maybe it can start buffering the video, waiting for the basePath to be given so that it can add the routes later? There will have to be many if/else's to cover many possible ways that a user can cause this to break.

1 Like