You can stream the video element as a link right to the nodered world map. I Just use the most recent image from frigate as I don't need to see a live view when I click the camera on the map.
Here are two flow tabs to add a camera to the map using a frigate link.
/cameramap is the link you send users. Disabled allot of settings so they cant do anything but click a camera.
/cameramapadmin is the link for adding cameras to the map. I keep users from accessing this url. I use ssh port forwarding to get to this link as it is only accessible from localhost
but you can hide it any way ya like.
http://10.x.x.1/frigate3/#YourCameraHere
You feed /cameramapadmin the above url and It does all the other work in code to add and remove from the map. It should work for any frigate url as it only cares about the camera name and the complete url to the camera.
So If you fed it http:127.0.0.1:8971/#YourCameraHere it should work. I've not tested that as my frigate server is behind nginx with a diff url.
how it parses the url/cameraname
var cameraName = frigateCamURL.split('#')[1];
var baseURL = frigateCamURL.split('#')[0];
var imageURL = baseURL + 'api/' + cameraName + '/latest.webp?height=275';
^^^ yes I know I could save the split as an array and then call them from the array but when I come back 1 year later the above is easy on the brain/eyes
Well you do have to refresh (F5) browser window every time you add a camera for the first time. I know how to fix this but f5 is quick and easy 
/cameramap
[
{
"id": "fd4afd8865bbfe5e",
"type": "tab",
"label": "/cameramap",
"disabled": false,
"info": "",
"env": []
},
{
"id": "05c71746369cd16b",
"type": "worldmap in",
"z": "fd4afd8865bbfe5e",
"name": "",
"path": "/cameramap",
"events": "connect,disconnect,point,draw,layer,bounds,files,other",
"x": 150,
"y": 160,
"wires": [
[
"3a3e9f781bfd22c4"
]
]
},
{
"id": "a4d6ba7a0784034b",
"type": "worldmap",
"z": "fd4afd8865bbfe5e",
"name": "",
"lat": "38",
"lon": "-100",
"zoom": "18",
"layer": "EsriS",
"cluster": "",
"maxage": "",
"usermenu": "hide",
"layers": "show",
"panit": "false",
"panlock": "false",
"zoomlock": "false",
"hiderightclick": "true",
"coords": "none",
"showgrid": "false",
"showruler": "false",
"allowFileDrop": "false",
"path": "/cameramap",
"overlist": "",
"maplist": "OSMG,OSMC,EsriC,EsriS",
"mapname": "",
"mapurl": "",
"mapopt": "",
"mapwms": false,
"x": 1070,
"y": 160,
"wires": []
},
{
"id": "ae0cf05b120281d5",
"type": "function",
"z": "fd4afd8865bbfe5e",
"name": "add known cameras to map",
"func": "const camLat = Number(msg.payload.lat);\nconst camLon = Number(msg.payload.lon);\nconst cameraName = msg.payload.name;\nconst imageURL = msg.payload.imageURL;\nconst reviewURL = msg.payload.reviewURL;\n\nmsg.payload = {\n \"name\":cameraName,\n \"layer\":\"cameras\",\n \"lat\":camLat,\n \"lon\":camLon,\n \"icon\":\"fa-video-camera\",\n \"iconColor\":\"forestgreen\",\n \"photoUrl\": imageURL,\n \"weblink\":[\n {\n \"name\":\"Review\",\n \"url\": reviewURL,\n \"target\":\"_new\"\n }]\n};\n\n\nreturn msg;\n\n",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 860,
"y": 160,
"wires": [
[
"a4d6ba7a0784034b"
]
]
},
{
"id": "3a3e9f781bfd22c4",
"type": "switch",
"z": "fd4afd8865bbfe5e",
"name": "",
"property": "payload.action",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "connected",
"vt": "str"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 310,
"y": 160,
"wires": [
[
"bca2390ed4de4437"
]
]
},
{
"id": "bca2390ed4de4437",
"type": "change",
"z": "fd4afd8865bbfe5e",
"name": "",
"rules": [
{
"t": "set",
"p": "payload",
"pt": "msg",
"to": "cameras",
"tot": "global"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 480,
"y": 160,
"wires": [
[
"2ae2510bfd834c47"
]
]
},
{
"id": "2ae2510bfd834c47",
"type": "split",
"z": "fd4afd8865bbfe5e",
"name": "",
"splt": "\\n",
"spltType": "str",
"arraySplt": 1,
"arraySpltType": "len",
"stream": false,
"addname": "",
"property": "payload",
"x": 650,
"y": 160,
"wires": [
[
"ae0cf05b120281d5"
]
]
}
]
/cameramapadmin
[
{
"id": "b28addf86596775b",
"type": "tab",
"label": "/cameramapadmin",
"disabled": false,
"info": "",
"env": []
},
{
"id": "22262ff764863a7d",
"type": "worldmap",
"z": "b28addf86596775b",
"name": "",
"lat": "30",
"lon": "-100",
"zoom": "18",
"layer": "EsriS",
"cluster": "",
"maxage": "",
"usermenu": "show",
"layers": "show",
"panit": "false",
"panlock": "false",
"zoomlock": "false",
"hiderightclick": "false",
"coords": "deg",
"showgrid": "false",
"showruler": "false",
"allowFileDrop": "false",
"path": "/cameramapadmin",
"overlist": "",
"maplist": "OSMG,OSMC,EsriC,EsriS",
"mapname": "",
"mapurl": "",
"mapopt": "",
"mapwms": false,
"x": 1030,
"y": 280,
"wires": []
},
{
"id": "eba074a4bdf03c43",
"type": "worldmap in",
"z": "b28addf86596775b",
"name": "",
"path": "/cameramapadmin",
"events": "connect,disconnect,point,draw,layer,bounds,files,other",
"x": 110,
"y": 280,
"wires": [
[
"94bd3683073ee7a9",
"fe5418fc957d97ee",
"03a28bda6eafa07a",
"c1b245853ab11ad5"
]
]
},
{
"id": "94bd3683073ee7a9",
"type": "function",
"z": "b28addf86596775b",
"name": "contextmenu Add Camera",
"func": "var menu ='<center><br/>';\n//menu += '';\nmenu += 'Frigate Camera URL<br/><input type=\"text\" id=\"frigateCamURL\" placeholder=\"http://x.x.x.x/frigateX/#CameraName\"><br/><br/>';\nmenu += '<input type=\"button\" value=\"Submit\" onclick=';\nmenu += '\\'feedback(\"myform\",{';\nmenu += '\"frigateCamURL\":document.getElementById(\"frigateCamURL\").value,';\nmenu += '\"lat\":rclk.lat.toFixed(12),';\nmenu += '\"lon\":rclk.lng.toFixed(12),';\nmenu += '},\"formAction\",true)\\' > <br/><br/> ';\n\nmsg.payload = { command: { \"contextmenu\":menu } }\n\nreturn msg;\n\n",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 410,
"y": 280,
"wires": [
[
"22262ff764863a7d"
]
]
},
{
"id": "fe5418fc957d97ee",
"type": "switch",
"z": "b28addf86596775b",
"name": "",
"property": "payload.value.frigateCamURL",
"propertyType": "msg",
"rules": [
{
"t": "cont",
"v": "#",
"vt": "str"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 110,
"y": 420,
"wires": [
[
"b450c167f679e3fe"
]
]
},
{
"id": "6a8244dbffa52d45",
"type": "debug",
"z": "b28addf86596775b",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 1130,
"y": 420,
"wires": []
},
{
"id": "b450c167f679e3fe",
"type": "function",
"z": "b28addf86596775b",
"name": "Add Camera",
"func": "const frigateCamURL = msg.payload.value.frigateCamURL;\nconst lat = msg.payload.value.lat;\nconst lon = msg.payload.value.lon;\nvar camerasGlobalObject = global.get('cameras');\nvar cameraName = frigateCamURL.split('#')[1];\nvar baseURL = frigateCamURL.split('#')[0];\nvar imageURL = baseURL + 'api/' + cameraName + '/latest.webp?height=275';\n\n// add camera\ncamerasGlobalObject[cameraName] = {\n name: cameraName,\n baseURL: baseURL,\n imageURL: imageURL,\n reviewURL: frigateCamURL,\n lat: lat,\n lon: lon,\n};\nglobal.set('cameras', camerasGlobalObject);\n\nmsg.payload = camerasGlobalObject;\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 370,
"y": 420,
"wires": [
[
"dd67295e522719f0"
]
]
},
{
"id": "cfa75bd2365b2435",
"type": "file",
"z": "b28addf86596775b",
"name": "",
"filename": "cameras.json",
"filenameType": "str",
"appendNewline": false,
"createDir": false,
"overwriteFile": "true",
"encoding": "utf8",
"x": 690,
"y": 420,
"wires": [
[
"6a8244dbffa52d45"
]
]
},
{
"id": "dd67295e522719f0",
"type": "json",
"z": "b28addf86596775b",
"name": "",
"property": "payload",
"action": "str",
"pretty": false,
"x": 510,
"y": 420,
"wires": [
[
"cfa75bd2365b2435"
]
]
},
{
"id": "aa79ac3a6f2d5edd",
"type": "file in",
"z": "b28addf86596775b",
"name": "",
"filename": "cameras.json",
"filenameType": "str",
"format": "utf8",
"chunk": false,
"sendError": false,
"encoding": "utf8",
"allProps": false,
"x": 330,
"y": 40,
"wires": [
[
"52e8689070516784"
]
]
},
{
"id": "123f14f2cbf5ad41",
"type": "inject",
"z": "b28addf86596775b",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": true,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 110,
"y": 40,
"wires": [
[
"aa79ac3a6f2d5edd"
]
]
},
{
"id": "05e15798bc3d36c5",
"type": "debug",
"z": "b28addf86596775b",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 1090,
"y": 40,
"wires": []
},
{
"id": "685aaa075a4f1c8d",
"type": "catch",
"z": "b28addf86596775b",
"name": "",
"scope": [
"aa79ac3a6f2d5edd"
],
"uncaught": false,
"x": 110,
"y": 100,
"wires": [
[
"a9c723185eaafc45"
]
]
},
{
"id": "a9c723185eaafc45",
"type": "switch",
"z": "b28addf86596775b",
"name": "cameras.json contains: no such file or directory, else",
"property": "error.message",
"propertyType": "msg",
"rules": [
{
"t": "cont",
"v": "no such file or directory",
"vt": "str"
},
{
"t": "else"
}
],
"checkall": "true",
"repair": false,
"outputs": 2,
"x": 400,
"y": 100,
"wires": [
[
"a17f5349df83f8ae"
],
[
"238d661f47a22fdd"
]
]
},
{
"id": "238d661f47a22fdd",
"type": "debug",
"z": "b28addf86596775b",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 790,
"y": 120,
"wires": []
},
{
"id": "a17f5349df83f8ae",
"type": "function",
"z": "b28addf86596775b",
"name": "",
"func": "msg.payload = {};\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 720,
"y": 80,
"wires": [
[
"fe1e4011a8e5bb7f"
]
]
},
{
"id": "3b05e7442f14ca73",
"type": "file",
"z": "b28addf86596775b",
"name": "",
"filename": "cameras.json",
"filenameType": "str",
"appendNewline": false,
"createDir": false,
"overwriteFile": "false",
"encoding": "utf8",
"x": 1070,
"y": 80,
"wires": [
[
"52e8689070516784"
]
]
},
{
"id": "fe1e4011a8e5bb7f",
"type": "json",
"z": "b28addf86596775b",
"name": "",
"property": "payload",
"action": "str",
"pretty": false,
"x": 890,
"y": 80,
"wires": [
[
"3b05e7442f14ca73"
]
]
},
{
"id": "52e8689070516784",
"type": "json",
"z": "b28addf86596775b",
"name": "",
"property": "payload",
"action": "obj",
"pretty": false,
"x": 530,
"y": 40,
"wires": [
[
"f7f25076860735dc"
]
]
},
{
"id": "f7f25076860735dc",
"type": "change",
"z": "b28addf86596775b",
"name": "",
"rules": [
{
"t": "set",
"p": "cameras",
"pt": "global",
"to": "payload",
"tot": "msg"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 750,
"y": 40,
"wires": [
[
"05e15798bc3d36c5"
]
]
},
{
"id": "521fd84220a23967",
"type": "function",
"z": "b28addf86596775b",
"name": "add known cameras to map",
"func": "const camLat = Number(msg.payload.lat);\nconst camLon = Number(msg.payload.lon);\nconst cameraName = msg.payload.name;\nconst imageURL = msg.payload.imageURL;\nconst reviewURL = msg.payload.reviewURL;\n\nmsg.payload = {\n \"name\":cameraName,\n \"layer\":\"cameras\",\n \"lat\":camLat,\n \"lon\":camLon,\n \"icon\":\"fa-video-camera\",\n \"iconColor\":\"forestgreen\",\n \"photoUrl\": imageURL,\n \"weblink\":[\n {\n \"name\":\"Review\",\n \"url\": reviewURL,\n \"target\":\"_new\"\n }]\n};\n\n\nreturn msg;\n\n",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 740,
"y": 340,
"wires": [
[
"22262ff764863a7d"
]
]
},
{
"id": "03a28bda6eafa07a",
"type": "switch",
"z": "b28addf86596775b",
"name": "",
"property": "payload.action",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "connected",
"vt": "str"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 110,
"y": 340,
"wires": [
[
"21e766a363bbc0a0"
]
]
},
{
"id": "21e766a363bbc0a0",
"type": "change",
"z": "b28addf86596775b",
"name": "",
"rules": [
{
"t": "set",
"p": "payload",
"pt": "msg",
"to": "cameras",
"tot": "global"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 380,
"y": 340,
"wires": [
[
"b3f8a6ea6a91af25"
]
]
},
{
"id": "b3f8a6ea6a91af25",
"type": "split",
"z": "b28addf86596775b",
"name": "",
"splt": "\\n",
"spltType": "str",
"arraySplt": 1,
"arraySpltType": "len",
"stream": false,
"addname": "",
"property": "payload",
"x": 530,
"y": 340,
"wires": [
[
"521fd84220a23967"
]
]
},
{
"id": "c1b245853ab11ad5",
"type": "switch",
"z": "b28addf86596775b",
"name": "action: delete?",
"property": "payload.action",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "delete",
"vt": "str"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 140,
"y": 380,
"wires": [
[
"1490cf6df21b98ca"
]
]
},
{
"id": "a9f194a666ada1ba",
"type": "function",
"z": "b28addf86596775b",
"name": "Delete Camera",
"func": "\nvar camerasGlobalObject = msg.camerasGlobalObject;\ndelete camerasGlobalObject[msg.payload.name];\n\nglobal.set('cameras', camerasGlobalObject);\n\nmsg.payload = camerasGlobalObject;\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 700,
"y": 380,
"wires": [
[
"e486d90a2a40b564"
]
]
},
{
"id": "1490cf6df21b98ca",
"type": "change",
"z": "b28addf86596775b",
"name": "",
"rules": [
{
"t": "set",
"p": "camerasGlobalObject",
"pt": "msg",
"to": "cameras",
"tot": "global"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 430,
"y": 380,
"wires": [
[
"a9f194a666ada1ba"
]
]
},
{
"id": "b3721a931a153081",
"type": "debug",
"z": "b28addf86596775b",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 1150,
"y": 380,
"wires": []
},
{
"id": "849f434fc6e87b95",
"type": "file",
"z": "b28addf86596775b",
"name": "",
"filename": "cameras.json",
"filenameType": "str",
"appendNewline": false,
"createDir": false,
"overwriteFile": "true",
"encoding": "utf8",
"x": 1010,
"y": 380,
"wires": [
[
"b3721a931a153081"
]
]
},
{
"id": "e486d90a2a40b564",
"type": "json",
"z": "b28addf86596775b",
"name": "",
"property": "payload",
"action": "str",
"pretty": false,
"x": 850,
"y": 380,
"wires": [
[
"849f434fc6e87b95"
]
]
}
]
I'm a node-red worldmap fanatic. Got me 11 small approved pull requests to this project by @dceejay . 1/2 of it is just documentation.