Walkie Talkie

flow

screenshot

Credit

This flow is an adaption of the great flow developed by Sammachin

[{"id":"ac4eab3a772da4ec","type":"tab","label":"Walkie Talkie","disabled":false,"info":""},{"id":"24650ddaa7403284","type":"group","z":"ac4eab3a772da4ec","name":"","style":{"fill":"#0070c0","label":true},"nodes":["212b52d73426cffb","3c8b69d0cc55d8a3","39a1f967e2ebbce9"],"x":28,"y":33,"w":1050,"h":614},{"id":"212b52d73426cffb","type":"group","z":"ac4eab3a772da4ec","g":"24650ddaa7403284","name":"www","style":{"label":true},"nodes":["67566ad976e34966","08d1db58fc98eb3a","b66d6e07fc89c848"],"x":504,"y":59,"w":542,"h":82},{"id":"3c8b69d0cc55d8a3","type":"group","z":"ac4eab3a772da4ec","g":"24650ddaa7403284","name":"Session","style":{"label":true},"nodes":["34db912780da5765","76ba4a5862e0834c","0b0fde7e1f8f9eea","ea328269a74eae56"],"x":54,"y":539,"w":992,"h":82},{"id":"39a1f967e2ebbce9","type":"group","z":"ac4eab3a772da4ec","g":"24650ddaa7403284","name":"Websocket","style":{"label":true},"nodes":["a63b76f70839dc96","02d4401363eb9ffe","da1729ded5dc2d8e","7a4ca05c29d57993","c02a930755b45eba","067811a23b6ab558","bba60d053a1ad990","5b0b4c5fad57b058"],"x":54,"y":179,"w":998,"h":308},{"id":"5b0b4c5fad57b058","type":"group","z":"ac4eab3a772da4ec","g":"39a1f967e2ebbce9","style":{"stroke":"#93a1a1","stroke-opacity":"1","fill":"#eee8d5","fill-opacity":"0.5","label":true,"label-position":"nw","color":"#657b83"},"nodes":["c2e8fea0b7c66e9f","c42812838122d473","4a14f41bfb7a5ba4"],"x":214,"y":379,"w":812,"h":82},{"id":"a63b76f70839dc96","type":"websocket in","z":"ac4eab3a772da4ec","g":"39a1f967e2ebbce9","name":"","server":"305b5c990ac144fb","client":"","x":150,"y":280,"wires":[["da1729ded5dc2d8e"]]},{"id":"02d4401363eb9ffe","type":"websocket out","z":"ac4eab3a772da4ec","g":"39a1f967e2ebbce9","name":"","server":"305b5c990ac144fb","client":"","x":950,"y":340,"wires":[]},{"id":"da1729ded5dc2d8e","type":"switch","z":"ac4eab3a772da4ec","g":"39a1f967e2ebbce9","name":"Binary/Text","property":"payload","propertyType":"msg","rules":[{"t":"istype","v":"string","vt":"string"},{"t":"istype","v":"buffer","vt":"buffer"}],"checkall":"true","repair":false,"outputs":2,"x":350,"y":280,"wires":[["7a4ca05c29d57993"],["067811a23b6ab558"]]},{"id":"7a4ca05c29d57993","type":"json","z":"ac4eab3a772da4ec","g":"39a1f967e2ebbce9","name":"","property":"payload","action":"","pretty":false,"x":550,"y":220,"wires":[["bba60d053a1ad990"]]},{"id":"c02a930755b45eba","type":"debug","z":"ac4eab3a772da4ec","g":"39a1f967e2ebbce9","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":950,"y":220,"wires":[]},{"id":"067811a23b6ab558","type":"function","z":"ac4eab3a772da4ec","g":"39a1f967e2ebbce9","name":"Copy message to all other sessions","func":"var src = msg._session.id\n\nvar sessions = flow.get('sessions');\n\nsessions.forEach(function(s){\n    if (s !=src){\n        msg._session.id = s\n        node.send(msg)\n    }       \n});\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":640,"y":340,"wires":[["02d4401363eb9ffe"]]},{"id":"bba60d053a1ad990","type":"function","z":"ac4eab3a772da4ec","g":"39a1f967e2ebbce9","name":"Add Session to flows list","func":"var src = msg._session.id\nvar sessions = flow.get('sessions') || [];\nsessions.push(src)\nflow.set('sessions', sessions)","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":750,"y":220,"wires":[["c02a930755b45eba"]]},{"id":"34db912780da5765","type":"inject","z":"ac4eab3a772da4ec","g":"3c8b69d0cc55d8a3","name":"View Session List","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"sessions","payloadType":"flow","x":180,"y":580,"wires":[["76ba4a5862e0834c"]]},{"id":"76ba4a5862e0834c","type":"debug","z":"ac4eab3a772da4ec","g":"3c8b69d0cc55d8a3","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":390,"y":580,"wires":[]},{"id":"0b0fde7e1f8f9eea","type":"change","z":"ac4eab3a772da4ec","g":"3c8b69d0cc55d8a3","name":"","rules":[{"t":"delete","p":"sessions","pt":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":920,"y":580,"wires":[[]]},{"id":"ea328269a74eae56","type":"inject","z":"ac4eab3a772da4ec","g":"3c8b69d0cc55d8a3","name":"Clear Sessions on Startup","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"true","payloadType":"bool","x":660,"y":580,"wires":[["0b0fde7e1f8f9eea"]]},{"id":"4a14f41bfb7a5ba4","type":"status","z":"ac4eab3a772da4ec","g":"5b0b4c5fad57b058","name":"Websocket Status Events","scope":["a63b76f70839dc96"],"x":350,"y":420,"wires":[["c42812838122d473"]]},{"id":"c42812838122d473","type":"switch","z":"ac4eab3a772da4ec","g":"5b0b4c5fad57b058","name":"Look for disconnect","property":"status.event","propertyType":"msg","rules":[{"t":"eq","v":"disconnect","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":640,"y":420,"wires":[["c2e8fea0b7c66e9f"]]},{"id":"c2e8fea0b7c66e9f","type":"function","z":"ac4eab3a772da4ec","g":"5b0b4c5fad57b058","name":"Remove Session","func":"var src = msg.status._session.id\nvar sessions = flow.get('sessions') || [];\n\nconst index = sessions.indexOf(src);\nif (index > -1) {\n  sessions.splice(index, 1);\n}\n\nflow.set('sessions', sessions)","outputs":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":910,"y":420,"wires":[],"icon":"font-awesome/fa-remove"},{"id":"08d1db58fc98eb3a","type":"http in","z":"ac4eab3a772da4ec","g":"212b52d73426cffb","name":"","url":"/intercom","method":"get","upload":false,"swaggerDoc":"","x":600,"y":100,"wires":[["67566ad976e34966"]]},{"id":"b66d6e07fc89c848","type":"http response","z":"ac4eab3a772da4ec","g":"212b52d73426cffb","name":"","statusCode":"","headers":{},"x":970,"y":100,"wires":[]},{"id":"67566ad976e34966","type":"template","z":"ac4eab3a772da4ec","g":"212b52d73426cffb","name":"Web Interface","field":"payload","fieldType":"msg","format":"html","syntax":"plain","template":"<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <title>WS Intercom</title>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n   <style>\n       body{\n           background: #646970;\n           text-align: center;\n       }\n\n       input {\n           width: 400px;\n           font-size:16px;\n           font-family:monospace;\n       }\n\n       .button {\n           width: 400px;\n\t\t   height: 100px;\n           background-color: #d0d6dd; \n           border: 0px;\n           color: #646970;\n           text-align: center;\n           text-decoration: none;\n           margin-top: 10px;\n           font-size: 20px;\n           font-family:monospace;\n       }\n       h1{\n\t\t   color: #f4f4f4;\n           font-size:30px;\n           font-family:monospace;\n       }\n       h3{\n\t\t   color: #f4f4f4;\n           font-size:24px;\n           font-family:monospace;\n       }\n       </style>\n  </head>\n  <body>\n    <h1>WS Intercom</h1>\n\t<h3>Connection:<span id='conn'>⚪️</span> Tx:<span id='tx'>⚪️</span></h3>\n\t<button class=\"button\" id=\"con_btn\", onclick='connect()' style=\"height:50px\">Connect</button>\n\t<br>\n\t<button  class=\"button\" id=\"ptt_btn\" style=\"height:200px\">Talk</button>   \n  <script>\n    var wsurl;\n    var headerdata;\n\t  var ptt_btn=document.getElementById(\"ptt_btn\");\n\t  var con_btn=document.getElementById(\"con_btn\");\n    var ptt=false;\n\t  var status_off = '⚪️';\n\t  var status_on = '🔵';\n\t  var status_tx = '🔴';\n      var AudioContext = window.AudioContext || window.webkitAudioContext\n      var context = new AudioContext()\n\t  var time;\n\t  var ws;\n\t  \n\t  ptt_btn.disabled = true; \n\t  ptt_btn.onmousedown = function(e){\n\t  ptt=true;\n\t\tdocument.getElementById(\"tx\").innerHTML=status_tx;\n\t  } \n\t  ptt_btn.onmouseup = function(e){\n\t  \tptt=false;\n\t\tdocument.getElementById(\"tx\").innerHTML=status_off;\n\t  }\n\t\t\t\t\t\n      \n    function extend(obj, src) {\n    \t    for (var key in src) {\n    \t        if (src.hasOwnProperty(key)) obj[key] = src[key];\n    \t    }\n    \t    return obj;\n    \t}\n    \n\t  function connect(){\n\t      if (window.location.protocol == 'https:'){\n\t          wsurl = 'wss://'+window.location.host+'/socket';\n\t      } else{\n\t          wsurl = 'ws://'+window.location.host+'/socket';\n\t      }\n\n      console.log(wsurl);\n\t\t  ws = new WebSocket(wsurl, \"WSBRIDGE\")\n\t      ws.binaryType = 'arraybuffer';\n\t\t  time = 0\n\t\t  con_btn.innerText = 'Disconnect';\n\t\t  con_btn.onclick = function () { ws.close()};\n\t\t  ptt_btn.disabled = false; \n\t\t\n\t\t  ws.onopen = function(){\n\t\t  document.getElementById(\"conn\").innerHTML=status_on;\n\t\t  var evt = {\"event\": \"websocket:connected\", \"content-type\" : \"audio/l16;rate=16000\"};\n   \n  \t\t  ws.send(JSON.stringify(evt));\n\t\t\t  console.log(\"Sent: \"+JSON.stringify(evt));\n\t\t\t  console.log('connected');\n\t\t\t\n\t\t  }\n\t\t  ws.onclose = function(){\n\t\t\t   document.getElementById(\"conn\").innerHTML=status_off;\n\t\t\t\tconsole.log('disconnected');\n\t\t\t\tcon_btn.innerText = 'Connect';\n\t\t\t\tcon_btn.onclick = function () { connect()};\n\t\t\t\tptt_btn.disabled = true; \n\t\t  }\n\t\t  \n\t      ws.onmessage = function(event){\n          if(event.data instanceof ArrayBuffer) {\n\t          time = Math.max(context.currentTime, time)\n\t          var input = new Int16Array(event.data)\n\t            if(input.length) {\n\t              var buffer = context.createBuffer(1, input.length, 16000)\n\t              var data = buffer.getChannelData(0)\n\t              for (var i = 0; i < data.length; i++) {\n\t                data[i] = input[i] / 32767\n\t              }\n\t              var source = context.createBufferSource()\n\t              source.buffer = buffer\n\t              source.connect(context.destination)\n\t              source.start(time += buffer.duration)\n\t            }\n\t          } else {\n\t            console.log(\"Recieved: \"+ event.data);\n\t          }\n          }  \n\t }\n\t \n\n      navigator.mediaDevices.getUserMedia({\n        video: false,\n        audio: true\n      })\n\t  .then( stream => {\n        var source = context.createMediaStreamSource(stream)\n        var processor = context.createScriptProcessor(1024, 1, 1)\n        var downsampled = new Int16Array(2048)\n        var downsample_offset = 0\n\n        function process_samples(){\n          while(downsample_offset > 320) {\n            var output = downsampled.slice(0, 320)\n            downsampled.copyWithin(0, 320)\n            downsample_offset -= 320\n\t\t\tif(ptt == true) {\n              ws.send(output.buffer)\n            }\n          }\n        }\n        var sampleRatio = context.sampleRate / 16000\n        processor.onaudioprocess = (audioProcessingEvent) => {\n          var inputBuffer = audioProcessingEvent.inputBuffer\n          var outputBuffer = audioProcessingEvent.outputBuffer\n          var inputData = inputBuffer.getChannelData(0)\n          var outputData = outputBuffer.getChannelData(0)\n          for (var i = 0; i < inputData.length; i += sampleRatio) {\n            var sidx = Math.floor(i)\n            var tidx = Math.floor(i/sampleRatio)\n            downsampled[downsample_offset + tidx] = inputData[sidx] * 32767\n          }\n          downsample_offset += ~~(inputData.length/sampleRatio)\n          if(downsample_offset > 320) {\n            process_samples()\n          }\n          for (var sample = 0; sample < inputBuffer.length; sample++) {\n            // Silence the output\n            outputData[sample] = 0\n          }\n        }\n        source.connect(processor)\n        processor.connect(context.destination)\n      })\n    </script>\n  </body>\n</html>\n","output":"str","x":800,"y":100,"wires":[["b66d6e07fc89c848"]]},{"id":"305b5c990ac144fb","type":"websocket-listener","path":"/socket","wholemsg":"false"}]

Flow Info

Created 3 years, 2 months ago
Rating: 5 2

Owner

Actions

Rate:

Node Types

Core
  • change (x1)
  • debug (x2)
  • function (x3)
  • http in (x1)
  • http response (x1)
  • inject (x2)
  • json (x1)
  • status (x1)
  • switch (x2)
  • template (x1)
  • websocket in (x1)
  • websocket out (x1)
  • websocket-listener (x1)
Other

Tags

Copy this flow JSON to your clipboard and then import into Node-RED using the Import From > Clipboard (Ctrl-I) menu option