summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--LICENSE21
-rw-r--r--config.json49
-rw-r--r--main.js80
-rw-r--r--package.json20
-rw-r--r--src/index.css128
-rw-r--r--src/index.html260
6 files changed, 558 insertions, 0 deletions
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..115d8fa
--- /dev/null
+++ b/LICENSE
@@ -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
diff --git a/main.js b/main.js
new file mode 100644
index 0000000..20b78bf
--- /dev/null
+++ b/main.js
@@ -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>