Here is working m3u8
It still needs work
Like cleaning up, error handling, sanity checking etc.
[{"id":"88d4cf6b97770beb","type":"ui-template","z":"a77bbd614562e1ee","group":"00328f3e4d88b179","page":"","ui":"","name":"mediaplayer","order":1,"width":0,"height":0,"head":"","format":"<template>\n <div class=\"nrdb-widget--media-player\">\n <div v-if=\"loaded && !loading\">\n <div v-if=\"error_\" class=\"centered\">\n {{ error_msg || 'Error loading media' }}\n </div>\n\n <div v-if=\"def_\" class=\"centered\">\n <v-img :src=\"def_src\" class=\"main_monitor\" title=\"Default\"></v-img>\n </div>\n\n <div v-if=\"pic_\" class=\"centered\">\n <v-img :src=\"pic_src\" class=\"main_monitor\" title=\"Mediafile\"></v-img>\n </div>\n\n <div v-if=\"str_\" class=\"centered\">\n <v-img :src=\"str_src\" class=\"main_monitor\" title=\"Click for snapshot\" @click=\"send({payload:'snapshot?mainview_url', topic:'Snapshot'})\">\n </v-img>\n </div>\n\n <div v-show=\"vid_\" class=\"centered\">\n <video\n height=\"400\" width=\"100%\"\n ref=\"vid_\" autoplay controls :src=\"vid_src\" class=\"main_monitor\" title=\"Main display\"\n type=\"video/mp4\"\n />\n </div>\n\n <div v-show=\"hls_\" class=\"centered\">\n <video\n ref=\"hls_\" autoplay muted class=\"main_monitor\"\n height=\"400\" width=\"100%\"\n title=\"Playing m3u8, click for snapshot\" type=\"video/mp4\"\n @click=\"send({payload:'snapshot?mainview_url', topic:'Snapshot'})\"\n />\n </div>\n\n <div v-show=\"saf_\" class=\"centered\">\n <video\n ref=\"saf_\" autoplay muted :src=\"saf_src || ''\" class=\"main_monitor\"\n height=\"400\" width=\"100%\"\n title=\"Playing m3u8, click for snapshot\" type=\"video/mp4\"\n @click=\"send({payload:'snapshot?mainview_url', topic:'Snapshot'})\"\n />\n </div>\n\n <div v-show=\"cap_\" class=\"centered\">\n <iframe ref=\"cap_\" height=\"400\" width=\"100%\" src=\"\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen></iframe>\n </div>\n </div>\n <div v-else>\n <v-progress-circular indeterminate />\n </div>\n </div>\n</template>\n\n\n<script>\nexport default {\n name: 'MediaPlayer',\n data () {\n return {\n loaded: false,\n loading: false,\n unsupported: false,\n hls_m: null,\n\n // simple booleans that reactively show/hide components\n def_: false,\n pic_: false,\n str_: false,\n vid_: false,\n hls_: false,\n saf_: false,\n cap_: true,\n\n // the source for each type of media - these are bound to the :src attributes in the template\n def_src: '',\n vid_src: '',\n hls_src: '',\n saf_src: '',\n cap_src: ''\n }\n },\n computed: {\n str_src () {\n return this.str_ ? (this.msg?.url + '?' + this.msg?._msgid || '') : ''\n },\n pic_src () {\n return this.pic_ ? (this.msg?.url + '?' + this.msg?._msgid || '') : ''\n }\n },\n mounted () {\n console.log('mounted')\n // code here when the component is first loaded\n const interval = setInterval(() => {\n if (window.Hls) {\n console.log('Hls has loaded')\n clearInterval(interval)\n this.loaded = true\n this.init()\n }\n }, 100)\n },\n methods: {\n init () {\n console.log('init')\n this.$socket.on('msg-input:' + this.id, (msg) => {\n // do stuff with msg. runs only when new messages are received\n this.onMsg(msg)\n })\n this.send({ payload: 'do_init' })\n this.loaded = true\n },\n isSafari () {\n if (/apple/i.test(navigator.vendor)) {\n // alert(\"Safari\");\n return true\n } else {\n return false\n }\n },\n stopVideos (prev) {\n // TODO: not sure what this is supposed to be doing\n // if(prev.indexOf('recordings')>-1 || prev.indexOf('youtube')>-1) {\n // $(\"iframe\").each(function() {\n // var src= $(this).attr('src');\n // if(prev.indexOf('youtube')>-1) {\n // $(this).attr('src','');\n // }else{\n // $(this).attr('src',src);\n // }\n // });\n // }\n // if(prev.indexOf('.mp4')>-1) {\n // $(\"[title~='Main']\").trigger('pause');\n // }\n },\n show (which, src) {\n console.log('showing', which, src)\n this.loading = false\n this.def_src = which !== 'def_' ? '' : (src || '')\n this.vid_src = which !== 'vid_' ? '' : (src || '')\n this.hls_src = which !== 'hls_' ? '' : (src || '')\n this.saf_src = which !== 'saf_' ? '' : (src || '')\n this.cap_src = which !== 'cap_' ? '' : (src || '')\n this.error_msg = which !== 'error_' ? '' : (src || '')\n this.def_ = which === 'def_'\n this.pic_ = which === 'pic_'\n this.str_ = which === 'str_'\n this.vid_ = which === 'vid_'\n this.hls_ = which === 'hls_'\n this.saf_ = which === 'saf_'\n this.cap_ = which === 'cap_'\n this.error_ = which === 'error_'\n },\n stopHls() {\n this.$refs.hls_?.stop && this.$refs.hls_.stop()\n if (this.hls_m) {\n try {\n this.hls_m.destroy()\n } catch (err) {}\n this.hls_m = null\n }\n },\n onMsg (msg) {\n console.log('msg', msg)\n if (msg.topic === 'stop') {\n this.stopHls()\n this.show('')\n }\n if (msg.payload?.indexOf('goback') > -1) {\n window.history.back()\n }\n if (msg.prev?.indexOf('recordings') > -1 || msg.prev?.indexOf('.mp4') > -1 || msg.prev?.indexOf('motions') > -1 || msg.prev?.indexOf('youtube') > -1) {\n this.stopVideos(msg.prev)\n }\n if (msg.payload?.indexOf('snapshots') > -1 || msg.payload?.indexOf('recordings') > -1 || msg.payload?.indexOf('motions') > -1) {\n const ip = location.host.split(':')[0]\n this.stopHls()\n this.show('cap_', 'http://' + ip + msg.url)\n }\n if (msg.payload?.indexOf('med') < 0 && msg.payload?.indexOf('default') > -1) {\n this.stopHls()\n const ip = location.host.split(':')[0]\n this.show('def_', 'http://' + ip + msg.url)\n }\n if (msg.payload?.indexOf('med') > -1 && msg.payload?.indexOf('default') < 0) {\n this.stopHls()\n\n if (msg.url?.indexOf('.mp4') > -1) {\n this.show('vid_', msg.url)\n } else {\n this.show('pic_')\n }\n }\n if (msg.payload?.indexOf('cam') > -1 && msg.url?.indexOf('.m3u8') < 0 && msg.url?.indexOf('youtube') < 0) {\n this.stopHls()\n this.show('str_')\n } else if (msg.url?.indexOf('.m3u8') > -1) {\n this.stopHls()\n if (this.isSafari()) {\n this.show('saf_')\n } else {\n this.send({ payload: 'show hls' })\n this.show('hls_')\n if (window.Hls?.isSupported()) {\n try {\n const video = this.$refs.hls_\n const hsl = new window.Hls()\n this.hls_m = hsl\n console.log('hls is supported')\n this.loading = true\n \n hsl.on(Hls.Events.MEDIA_ATTACHED, function () {\n console.log('video and hls.js are now bound together !');\n });\n hsl.on(Hls.Events.MANIFEST_PARSED, function (event, data) {\n console.log(\n 'manifest loaded, found ' + data.levels.length + ' quality level',\n );\n });\n hsl.loadSource(msg.url);\n hsl.attachMedia(video);\n this.loading = false\n } catch (err) {\n this.show('error_')\n node.send( { error: { message: err.message } } )\n }\n } else {\n this.show('error_', 'Hls Error: ' + err.message)\n node.send( { error: { message: 'Hls Not supported' } } )\n }\n }\n } else if (msg.url?.indexOf('youtube') > -1) {\n this.stopHls()\n this.$refs.cap_.src = msg.url\n this.show('cap_')\n } else if (msg.url?.endsWith('.mp4')) {\n this.stopHls()\n this.show('vid_', msg.url)\n }\n }\n }\n}\n</script>\n\n<script type=\"text/javascript\" src=\"https://cdn.jsdelivr.net/npm/hls.js@latest\"></script>\n","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":1450,"y":340,"wires":[["be79f6a038a78723"]]},{"id":"bb7d17187016273a","type":"inject","z":"a77bbd614562e1ee","name":"youtube for cat lovers","props":[{"p":"url","v":"https://www.youtube.com/embed/oZFAcp-Qfbs?si=dVMgWw6fuTof_om-","vt":"str"},{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"cam-1","payloadType":"str","x":1200,"y":300,"wires":[["88d4cf6b97770beb"]]},{"id":"5df6be2507f19659","type":"inject","z":"a77bbd614562e1ee","name":"youtube for dog lovers","props":[{"p":"url","v":"https://www.youtube.com/embed/c2OTHeCKsBE?si=M4Bq3U0gJTe5uwIr","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":1200,"y":340,"wires":[["88d4cf6b97770beb"]]},{"id":"6d27f6b22732560e","type":"inject","z":"a77bbd614562e1ee","name":"stop","props":[{"p":"url","v":"","vt":"str"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"stop","x":1150,"y":380,"wires":[["88d4cf6b97770beb"]]},{"id":"be79f6a038a78723","type":"debug","z":"a77bbd614562e1ee","name":"debug 2566","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1450,"y":380,"wires":[]},{"id":"e11ce29786db928d","type":"inject","z":"a77bbd614562e1ee","name":"BigBuckBunny.mp4","props":[{"p":"url","v":"http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4","vt":"str"},{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"str","x":1190,"y":260,"wires":[["88d4cf6b97770beb"]]},{"id":"91525ca5a6fd15cc","type":"inject","z":"a77bbd614562e1ee","name":"longtailvideo m3u8","props":[{"p":"url","v":"http://playertest.longtailvideo.com/adaptive/wowzaid3/playlist.m3u8","vt":"str"},{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"str","x":1190,"y":180,"wires":[["88d4cf6b97770beb"]]},{"id":"780b18b4cac8b3b2","type":"inject","z":"a77bbd614562e1ee","name":"another m3u8","props":[{"p":"url","v":"https://bitdash-a.akamaihd.net/content/MI201109210084_1/m3u8s/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.m3u8","vt":"str"},{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"str","x":1170,"y":220,"wires":[["88d4cf6b97770beb"]]},{"id":"00328f3e4d88b179","type":"ui-group","name":"mediaplayer","page":"b6154633432f57b1","width":"6","height":"1","order":1,"showTitle":false,"className":"","visible":"true","disabled":"false","groupType":"default"},{"id":"b6154633432f57b1","type":"ui-page","name":"mediaplayer","ui":"72c1e5a9ec204878","path":"/mediaplayer","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":1,"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,"showPageTitle":true,"navigationStyle":"default","titleBarStyle":"default"},{"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"}}]