diff options
| author | iceyrazor <iceyfoxservers@gmail.com> | 2024-08-25 04:33:03 -0500 |
|---|---|---|
| committer | iceyrazor <iceyfoxservers@gmail.com> | 2024-08-25 04:33:03 -0500 |
| commit | 1211fc9edd84358a9542f17c7881abacae8fa903 (patch) | |
| tree | de15656d0c05edd029c6cffbe239b9ad688cf77c | |
| -rw-r--r-- | LICENSE | 21 | ||||
| -rw-r--r-- | config.json | 49 | ||||
| -rw-r--r-- | main.js | 80 | ||||
| -rw-r--r-- | package.json | 20 | ||||
| -rw-r--r-- | src/index.css | 128 | ||||
| -rw-r--r-- | src/index.html | 260 |
6 files changed, 558 insertions, 0 deletions
@@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 iceyrazor + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/config.json b/config.json new file mode 100644 index 0000000..d0edf05 --- /dev/null +++ b/config.json @@ -0,0 +1,49 @@ +{ + "airtime_data": { + "stream_url": "https://radiosidewinder.out.airtime.pro:8000/radiosidewinder_b?_ga=1.133037898.513622194.1447957646/;stream.mp3", + "api_url": "https://radiosidewinder.airtime.pro/api/live-info?type=interval&limit=5&_=1646343267200" + }, + "volumes": [ + { + "default": 0.15 + }, + { + "lookfor": [ + [ + "currentShow", + 0, + "name" + ], + "Galnet News" + ], + "volume": 0.3 + }, + { + "lookfor": [ + [ + "currentShow", + 0, + "name" + ], + "Clan Ads" + ], + "volume": 0.3 + }, + { + "lookfor": [ + [ + "currentShow", + 0, + "name" + ], + "Commercial Break" + ], + "volume": 0.05 + } + ], + "read_cmd_file": "false", + "default_size": [ + 0, + 0 + ] +}
\ No newline at end of file @@ -0,0 +1,80 @@ +const electron = require('electron'), + {app,BrowserWindow,Menu,Tray,ipcMain} = require('electron'), + fs=require('fs'), + path=require('path'); + +myWindow=null; +global.tray=null; +app.whenReady().then(()=>{ + + //get display. if default config isnt too small. use configs size. + let primaryscreen=electron.screen.getPrimaryDisplay() + let config=JSON.parse(fs.readFileSync(path.join(__dirname,"config.json"))); + let setsize=[] + if(config["default_size"][0]<150&config["default_size"][1]<150){ + setsize=[Math.round(primaryscreen.bounds.width/2.8),Math.round(primaryscreen.bounds.height/1.2)] + } else { + setsize=config["default_size"] + } + + myWindow=new BrowserWindow({ + width: setsize[0], + height: setsize[1], + frame: false, + transparent:true, + resizable:true, + useContentSize: true, + webPreferences:{ + nodeIntegration: true, + contextIsolation: false, + enableRemoteModule: false, + } + }); + myWindow.loadFile('src/index.html'); + + //change default size on delay so config isnt spammed + let window_size_forc=myWindow.getSize() + myWindow.on('resize', function () { + window_size_forc = myWindow.getSize(); + + if(typeof window_size_forc[0]=="object"|typeof window_size_forc[0]=="array"){ + window_size_forc=window_size_forc[0] + } + }); + + function set_conf_size_loop(){ + let config=JSON.parse(fs.readFileSync(path.join(__dirname,"config.json"))); + + if(config["default_size"][0]!=window_size_forc[0]|config["default_size"][1]!=window_size_forc[1]){ + config["default_size"]=window_size_forc + fs.writeFileSync(path.join(__dirname,"config.json"),JSON.stringify(config,null,2)) + } + + setTimeout(set_conf_size_loop,3000) + } + set_conf_size_loop() +}); + +stop_cursor=false +let win_size=[] +ipcMain.on("setpos",(req,data)=>{ + stop_cursor=false + win_size=myWindow.getSize(); + function move_loop(){ + if(stop_cursor==true){ + return + } + let cursor_pos=electron.screen.getCursorScreenPoint() + + myWindow.setPosition(cursor_pos.x-data.x,cursor_pos.y-data.y) + myWindow.setSize(win_size[0],win_size[1]) + setTimeout(move_loop,20) + } + move_loop() +}) + +ipcMain.on("stoppos",(req,data)=>{ + stop_cursor=true + myWindow.setSize(win_size[0],win_size[1]) +}) + diff --git a/package.json b/package.json new file mode 100644 index 0000000..4d55663 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "stream-volumizer", + "version": "0.0.0", + "description": "a little peice of software that allows you to have differnt volume levels during different parts of a Airtime stream", + "main": "main.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "electron ." + }, + "keywords": [ + "volume", + "auto", + "control" + ], + "author": "iceyrazor", + "license": "MIT", + "dependencies": { + "electron": "^17.1.0" + } +} diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..497da7d --- /dev/null +++ b/src/index.css @@ -0,0 +1,128 @@ +#raw_json{ + display: none; + overflow-y: scroll; + height: 400px; + width: 90%; + border: 2px solid black; + white-space: pre-wrap; +} + + +#volumes_control{ + position: relative; + overflow-y: scroll; + max-width: 600px; + height: 500px; + border: 2px solid black; + padding: 10px; +} +#volumes_control > div{ + display: flex; + background-color: rgb(230,230,230); + margin-bottom: 3px; + align-content: flex-start; + justify-content: center; + grid-gap: 3px; + padding: 3px; +} +#volumes_control textarea{ + border: 2px inset rgb(120,120,120); + font-size: 15px; + width: 180px; + height: 15px; + resize: none; + overflow: hidden; +} +#volumes_control button{ + height: 20px; + min-width: 20px; + align-items: center; + justify-items: center; +} +#volume_wrap{ + position: relative; + max-width: 600px; +} +#vol_update_button{ + background-color: rgb(200,200,200); + border: 2px solid black; + position: absolute; + display: block; + width: 100px; + height: 15px; + padding: 3px; + bottom: 3px; + left: calc(50% - 50px); + user-select: none; +} +#vol_update_button:hover{ + background-color: rgb(190,190,190); +} + +.vol_update_button_tooltip{ + visibility: hidden; + position: absolute; + width: 120px; + left: calc(50% - (120px/2)); + bottom: 50px; + border: 2px solid black; + padding: 3px; + background-color: rgb(260,260,260); + border-radius: 10px; +} +.vol_update_button_wrap:hover .vol_update_button_tooltip{ + visibility: visible; +} + +#vol_add_button{ + background-color: rgb(200,200,200); + border: 2px solid black; + position: absolute; + display: block; + width: 50px; + height: 15px; + padding: 3px; + bottom: 3px; + left: 65%; + user-select: none; +} + + +.play_grid,.show_grid{ + display: grid; + grid-template-columns: 1fr 1fr; + border: 2px solid black; + padding: 4px; +} +#airtime_next,#airtime_next_show{ + border-left: 2px dashed rgb(120,120,120); + padding-left: 10px; +} +#airtime_np,#airtime_np_show{ + padding-right: 5px; +} + +#move_button{ + position: absolute; + padding: 3px; + top: 5px; + right: 5px; + user-select: none; + border: 2px solid black; +} + +#background_plate{ + position: fixed; + left: 0px; + top: 0px; + width: 100vw; + height: 100vh; + background-color: rgba(255,255,255,1); + z-index: -1; +} + +#error_box{ + display: none; + padding: 4px; + background-color: rgba(255,50,50); +}
\ No newline at end of file diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..8ea032f --- /dev/null +++ b/src/index.html @@ -0,0 +1,260 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <title></title> + <link rel="stylesheet" href="index.css"> +</head> +<body> + <div id="background_plate"></div> + <div id="error_box"></div> + + <audio controls="controls" id="airtime_radio"></audio> + <div id="move_button">click to drag</div> + + <div class="play_grid"> + <div id="airtime_np"></div> + <div id="airtime_next"></div> + </div> + <div class="show_grid"> + <div id="airtime_np_show"></div> + <div id="airtime_next_show"></div> + </div> + <div id="curr_date"></div> + + <br> + <p>volumes for different things</p> + <div id="volume_wrap"> + <div id="volumes_control"></div> + <div class="vol_update_button_wrap"> + <div align="center" id="vol_update_button" onClick="update_conf()"> + update + </div> + <span class="vol_update_button_tooltip">please dont spam this button</span> + <div align="center" id="vol_add_button" onClick="add_conf()">add</div> + </div> + </div> + + <br><br> + <button onclick="toggle_raw_json()">toggle raw json data</button><br><br> + <div id="raw_json"></div> + + <script language="javascript"> + const fs=require('fs'), + path=require("path"), + {ipcRenderer}=require('electron'); + + //error box func + function set_error_box(err){ + error_box.style.display="block"; + error_box.innerHTML=err + } + + //fetch config + let config_data={} + function fetch_config(){ + try{ + config_data=JSON.parse(fs.readFileSync(path.join(__dirname,"..","config.json"))); + config_data["error"]=false + } catch(err){ + config_data={err:err.toString(),"error":true} + set_error_box("config error:: "+err) + return + } + airtime_radio.innerHTML='<source src="'+config_data["airtime_data"]["stream_url"] + +'" type="audio/mp3">Your browser does not support the audio element.' + + volumes_str="" + for(let i in config_data.volumes){ + if(!config_data.volumes[i].default){ + volumes_str+='<div id="volumes_control_'+i+'">'+ + '<textarea style="resize: vertical;"></textarea> name:<textarea></textarea>volume: <input type="number" min="0" max="1"></input>'+ + '<button onclick="move_conf_vol('+i+',\'+\')">+</button>'+ + '<button onclick="move_conf_vol('+i+',\'-\')">-</button>'+ + '<button onclick="del_conf('+i+')">del</button>'+ + '</div>' + setTimeout(()=>{ + document.getElementById("volumes_control_"+i).children[0].value=config_data.volumes[i].lookfor[0] + document.getElementById("volumes_control_"+i).children[1].value=config_data.volumes[i].lookfor[1] + document.getElementById("volumes_control_"+i).children[2].value=config_data.volumes[i].volume + },250) + } else { + volumes_str+='<div id="volumes_control_'+i+'">'+'default: <textarea></textarea></div>' + setTimeout(()=>{ + document.getElementById("volumes_control_"+i).children[0].value=config_data.volumes[i].default + },250) + } + } + volumes_str+='<div id="spacer" style="background-color: rgba(0,0,0,0); margin-bottom: 20px"></div>' + volumes_control.innerHTML=volumes_str + } + console.log(fetch_config()); + + //move volume config + function move_conf_vol(id,dir){ + if(dir=="+"){ + arraymove(config_data.volumes,id,id-1) + } + if(dir=="-"){ + arraymove(config_data.volumes,id,id+1) + } + if(config_data.volumes[1].default){ + arraymove(config_data.volumes,1,0) + } + fs.writeFileSync(path.join(__dirname,"..","config.json"),JSON.stringify(config_data,null,2)) + fetch_config() + } + + //misc function to move arrays via index to index + function arraymove(arr, fromIndex, toIndex) { + let element = arr[fromIndex]; + arr.splice(fromIndex, 1); + arr.splice(toIndex, 0, element); + return arr + } + + //edit config + function update_conf(){ + for(i in config_data.volumes){ + if(config_data.volumes[i].default){ + config_data.volumes[i].default=volumes_control.children[i].children[0].value + } else { + config_data.volumes[i].lookfor[0]=volumes_control.children[i].children[0].value.split(",") + config_data.volumes[i].lookfor[1]=volumes_control.children[i].children[1].value + config_data.volumes[i].volume=parseFloat(volumes_control.children[i].children[2].value) + } + + } + fs.writeFileSync(path.join(__dirname,"..","config.json"),JSON.stringify(config_data,null,2)) + fetch_config() + fetch_api_data() + } + + //del config + function del_conf(id){ + config_data.volumes.splice(id,1) + fs.writeFileSync(path.join(__dirname,"..","config.json"),JSON.stringify(config_data,null,2)) + fetch_config() + fetch_api_data() + } + + //addconf + function add_conf(){ + config_data.volumes.push({"lookfor":[["currentShow",0,"name"],"n/a"],"volume":0.5}) + fs.writeFileSync(path.join(__dirname,"..","config.json"),JSON.stringify(config_data,null,2)) + fetch_config() + fetch_api_data() + } + + //fetch api data and use it?? + async function fetch_api_data(){ + if(config_data["airtime_data"]["api_url"]&&config_data["airtime_data"]["api_url"]!=""){ + await fetch(config_data["airtime_data"]["api_url"],{cache:"no-cache"}) + .then(res=>res.json()) + .then(res=>{ + api_data=res; + raw_json.innerHTML=JSON.stringify(res, null, 4); //put in the raw json data in json box + np_next(res) + //set volume based on "volumes" config + set_volume(res) + set_date(); + }); + return true + } else { + return "api not set" + } + } + + //this loop is set seperately so the fetch api could be called seperately on song changes + //please do not change the timeout, the api it fetches is not mine, no need to spam it + function loop_fetch_api_data(){ + if(config_data["error"]==false){ + fetch_api_data() + .then(res=>{ + if(res=="api not set"){ + set_error_box("api not set") + } else { + setTimeout(loop_fetch_api_data,13000) + } + }) + } + } + loop_fetch_api_data() + + + function set_date(){ + let date=new Date(); + let dateconstruct=date.getUTCFullYear()+"-"+(date.getUTCMonth()+1)+"-"+(date.getUTCDay()-1)+" "+date.getUTCHours()+ + ":"+date.getUTCMinutes()+":"+date.getUTCSeconds()+" UTC"; + curr_date.innerHTML=dateconstruct + } + + function np_next(data){ + airtime_np.innerHTML="<h1>now playing</h1>"+ + "<p>track</p>"+data["current"]["name"] + + airtime_next.innerHTML="<h1>next</h1>"+ + "<p>track</p>"+data["next"]["name"] + + airtime_np_show.innerHTML="<h1>current show</h1>"+ + data["currentShow"][0]["name"] + + airtime_next_show.innerHTML="<h1>next show</h1>"+ + data["nextShow"][0]["name"]+'<p>starts at</p>'+data["nextShow"][0]["start_timestamp"]+ + '<span style="padding:4px"></span>'+data["timezone"] + } + + function toggle_raw_json(){ + if(getComputedStyle(raw_json).display=="none"){ + raw_json.style.display="block" + } else { + raw_json.style.display="none" + } + } + + function set_volume(data){ + for(elem of document.getElementById("volumes_control").children){ + if(elem.id!="update_button"&elem.id!="spacer"){ + elem.style.backgroundColor="rgb(230,230,230)" + } + } + + for(vol in config_data.volumes){ + if(!config_data.volumes[vol].default){ + + if(config_data.volumes[vol].lookfor[0]){ + if(getNested(data,config_data.volumes[vol].lookfor[0]).includes(config_data.volumes[vol].lookfor[1])){ + airtime_radio.volume=config_data.volumes[vol].volume + document.getElementById('volumes_control_'+(vol)).style.backgroundColor="rgb(0,255,0)" + return + } + } + } + } + airtime_radio.volume=config_data.volumes[0].default + document.getElementById('volumes_control_0').style.backgroundColor="rgb(0,255,0)" + } + + function getNested(obj, arr) { + const keys=[...arr]; + if (keys.length === 0) return obj; + + if(obj[keys[0]]){ + return getNested(obj[keys.shift()], keys) + } else { + return undefined + } + } + + + //move window with cursor bs because electron doesnt have this >:( + move_button.addEventListener("mousedown",(eve)=>{ + ipcRenderer.send("setpos",{x:eve.clientX,y:eve.clientY}) + }) + move_button.addEventListener("mouseup",(eve)=>{ + ipcRenderer.send("stoppos",{}) + }) + </script> +</body> +</html> |
