diff --git a/js/main-dist.js b/js/main-dist.js index 05560a9..4286f39 100644 --- a/js/main-dist.js +++ b/js/main-dist.js @@ -1 +1 @@ -const urlCoverArt="img/defaultArt.png",stationKeys=Object.keys(stations);let skipCORS="";async function generateRadioButtons(){skipCORS=await isCORSEnabled("https://scrobblerad.io/");const t=document.getElementById("stationSelect");t.innerHTML="";const e=document.createDocumentFragment();stationKeys.filter((t=>{const e=stations[t];return!(!1===skipCORS&&e.cors)})).forEach((t=>{const a=stations[t],i=document.createElement("button");i.name=t,i.textContent=a.stationName;const s=document.createElement("input");s.type="radio",s.name="station",s.value=t,s.checked=t===radioPlayer.stationName,i.appendChild(s),e.appendChild(i)})),t.appendChild(e),t.addEventListener("click",(t=>{const e=t.target.closest("button");if(e){t.preventDefault();const a=e.name;window.location.hash=`#${a}`,radioPlayer.handleStationSelect(null,a,!0)}})),document.getElementById("togglePanels").addEventListener("click",(function(){const t=document.getElementById("panel1"),e=document.getElementById("panel2"),a=document.getElementById("panel3"),i=document.querySelector("#togglePanels .icon-hide-panels, #togglePanels .icon-show-panels");t.classList.toggle("show"),e.classList.toggle("grow"),a.classList.toggle("show"),i&&(i.classList.contains("icon-hide-panels")?(i.classList.remove("icon-hide-panels"),i.classList.add("icon-show-panels")):(i.classList.remove("icon-show-panels"),i.classList.add("icon-hide-panels")))}))}async function isCORSEnabled(t){try{return!!(await fetch(t,{method:"GET",mode:"cors"})).ok}catch(t){return"TypeError"===t.name&&t.message.includes("Failed to fetch"),!1}}function animateElement(t,e=2e3){t.classList.add("animated","fadeIn"),setTimeout((()=>{t.classList.remove("animated","fadeIn")}),e)}class Page{constructor(t,e){this.stationName=t,this.displayStationName=stations[t].stationName,this.radioPlayer=e,this.cacheDOMElements(),this.setupMediaSession("","",""),this.template=document.querySelector("#meta")}cacheDOMElements(){const t={currentSong:"title",currentArtist:"artist",currentAlbum:"album",currentListeners:"listeners",coverArt:"albumArt",radioNameLink:"radioNameLink",radioName:"radioName",stationLocation:"stationLocation",metaInfo:"metainfo"};for(const[e,a]of Object.entries(t))this[e+"Element"]=document.getElementById(a)}formatCompactNumber(t){return t<1e3?t:t>=1e3&&t<1e6?(t/1e3).toFixed(1).replace(/\.0$/,"")+"K":t>=1e6&&t<1e9?(t/1e6).toFixed(1).replace(/\.0$/,"")+"M":t>=1e9&&t<1e12?(t/1e9).toFixed(1).replace(/\.0$/,"")+"B":void 0}refreshCurrentData(t){const[e,a,i,s,n,r,,o]=t;setTimeout((()=>{const t=()=>{if(!e&&!a||!s||!o[this.stationName])return;const t=document.querySelector("div.playermeta");t.textContent="";const l=document.querySelector("#meta"),c=document.importNode(l.content,!0);c.querySelector("#title").textContent=e,c.querySelector("#artist").textContent=a,c.querySelector("#album").textContent=i,c.querySelector("#listeners").textContent=null!==n&&null!==r?`Listeners: ${this.formatCompactNumber(n)} | Plays: ${this.formatCompactNumber(r)}`:"",c.querySelector("#albumArt").src=s,c.querySelector("#albumArt").alt=`${e} by ${a}`;c.querySelector("#radioNameLink").href=o[this.stationName].webUrl,c.querySelector("#radioName").textContent=o[this.stationName].stationName,c.querySelector("#stationLocation").textContent=o[this.stationName].location,t.appendChild(c);const h=s===urlCoverArt?`url("../${s}")`:`url("${s}")`;document.documentElement.style.setProperty("--albumArt",h),animateElement(t),document.querySelector("#panel2").click(),this.setupMediaSession(e,a,s)},l=new Image;l.onload=()=>{setTimeout(t,0)},l.src=s}),1500)}setupMediaSession(t,e,a){if("mediaSession"in navigator){navigator.mediaSession.metadata=new MediaMetadata({title:t,artist:e||"",album:`Now playing on ${this.displayStationName}`||"",duration:1/0,startTime:0,artwork:[{src:a}]}),t&&e&&(document.title=`${t} - ${e} | ${this.displayStationName} on scrobblerad.io`);const i={nexttrack:()=>this.radioPlayer.skipForward(),previoustrack:()=>this.radioPlayer.skipBackward(),play:()=>this.radioPlayer.togglePlay(),pause:()=>this.radioPlayer.togglePlay()};for(const[t,e]of Object.entries(i))navigator.mediaSession.setActionHandler(t,e)}}}class RadioPlayer{constructor(t,e,a,i){this.currentStationData=null,this.audio=new Audio,this.playButton=t,this.skipForwardButton=e,this.skipBackButton=a,this.reloadStreamButton=i,this.isPlaying=null,this.stationName="",this.previousDataResponse=null,this.pauseTimeout=null,this.shouldReloadStream=!1,this.stations=document.querySelectorAll(".station"),this.debounceTimeout=null,this.firstRun=!0,this.streamingInterval=null,this.canAutoplay=!1,this.debouncedPlayAudio=this.debounce((t=>{this.audio&&(this.audio.pause(),this.audio=null),setTimeout((()=>{this.audio=t,this.getStreamingData(),this.play(),this.isPlaying=!0}),500)}),1500),this.bindMethods(),this.addEventListeners(),this.init()}bindMethods(){this.handleStationSelect=this.handleStationSelect.bind(this),this.getLfmMeta=this.getLfmMeta.bind(this),this.getStreamingData=this.getStreamingData.bind(this),this.extractSongAndArtist=this.extractSongAndArtist.bind(this),this.getPath=this.getPath.bind(this),this.upsizeImgUrl=this.upsizeImgUrl.bind(this),this.togglePlay=this.togglePlay.bind(this),this.skipForward=this.skipForward.bind(this),this.skipBackward=this.skipBackward.bind(this),this.reloadStream=this.reloadStream.bind(this)}async addEventListeners(){this.playButton.addEventListener("click",this.togglePlay),this.skipForwardButton.addEventListener("click",this.skipForward),this.skipBackButton.addEventListener("click",this.skipBackward),this.reloadStreamButton.addEventListener("click",this.reloadStream),document.getElementById("stationSelect").addEventListener("click",(t=>{t.target&&t.target.matches("input[name='station']")&&(this.handleStationSelect(t,t.target.value,!0),t.target.scrollIntoView({behavior:"smooth",block:"nearest"}))})),this.jumpToStationFromHash(),document.addEventListener("DOMContentLoaded",(()=>{this.jumpToStationFromHash()}),{once:!0})}init(){"serviceWorker"in navigator&&document.addEventListener("DOMContentLoaded",(()=>{navigator.serviceWorker.register("serviceWorker.js").then((()=>console.log("Service worker registered"))).catch((t=>console.log("Service worker not registered",t)))}),{once:!0})}calculateNextAndPreviousIndices(){this.currentIndex=stationKeys.indexOf(this.stationName);const t=stationKeys[(this.currentIndex+1)%stationKeys.length],e=stationKeys[(this.currentIndex-1+stationKeys.length)%stationKeys.length],a=stations[t].cors&&!skipCORS?2:1,i=stations[e].cors&&!skipCORS?2:1;this.nextIndex=(this.currentIndex+a)%stationKeys.length,this.previousIndex=(this.currentIndex-i+stationKeys.length)%stationKeys.length}debounce(t,e){return(...a)=>{this.debounceTimeout&&clearTimeout(this.debounceTimeout),this.debounceTimeout=setTimeout((()=>{t.apply(this,a)}),e)}}jumpToStationFromHash(){const t=window.location.hash;if(t){const e=t.substring(1),a=document.querySelector(`button[name='${e}']`);a&&(a.scrollIntoView({behavior:"smooth",block:"center"}),this.handleStationSelect(null,e,!0))}}async loadStationData(t){try{const e=await fetch(t),a=await e.text();return new Function(a+"; return stationData;")()}catch(t){console.error("Error loading station data:",t)}}async handleStationSelect(t,e,a){if(!e||!1===t)return;const i=await this.loadStationData(`/js/stations/${e}.js`);if(!i)return;this.currentStationData=i,document.title=`${this.currentStationData[e].stationName} currently loading`,this.streamingInterval&&(clearInterval(this.streamingInterval),this.streamingInterval=null),a&&(this.playButton.lastElementChild.className="spinner-grow text-light",this.lfmMetaChanged=!1,console.log(e),this.stationName=e,this.updateArt=!0,this.isPlaying=!0,a=!1);this.debounce((()=>{if(!this.isPlaying)return;if(!this.currentStationData[this.stationName])return void console.error("currentStationData is undefined or null");this.currentStationData[this.stationName].stationName;const a=new Audio(this.addCacheBuster(this.currentStationData[this.stationName].streamUrl));a.onloadedmetadata=()=>{this.lfmMetaChanged=!1,this.debouncedPlayAudio(a),this.streamingInterval=setInterval((()=>{this.getStreamingData()}),25e3)},a.onerror=e=>{console.warn("Error loading audio:",e),this.isPlaying&&(!0===t?this.skipBackward():this.skipForward())},a.load();new Page(this.stationName,this);const i=document.querySelector(`input[name='station'][value='${e}']`);i&&(i.checked=!0),window.location.hash=`#${e}`}),250)()}cleanupArtist(t){let e=t;return[/ x .*/,/ feat\..*/].forEach((t=>{e=e.replace(t,"")})),e.trim()}getFilterSet(){return{artist:[MetadataFilter.normalizeFeature],track:[MetadataFilter.removeRemastered,MetadataFilter.removeFeature,MetadataFilter.removeLive,MetadataFilter.removeCleanExplicit,MetadataFilter.removeVersion,MetadataFilter.youtube],album:[MetadataFilter.removeRemastered,MetadataFilter.removeFeature,MetadataFilter.removeLive,MetadataFilter.removeCleanExplicit,MetadataFilter.removeVersion]}}applyFilters(t,e){return MetadataFilter.createFilter(this.getFilterSet()).filterField(t,e)}extractSongAndArtist(t,e){const a=e=>{return a=this.getPath(t,this.currentStationData[this.stationName][e]),a?.replace(/'|’|‘|‚|‛|`|´/g,"'")||"";var a};let i=a("song"),s=a("artist"),n=a("album"),r=a("albumArt");if(this.currentStationData[this.stationName].altPath&&!i&&(i=a("song2"),s=a("artist2")),this.currentStationData[this.stationName].orbPath){const e=(this.currentStationData[this.stationName].pathRegex||/^(.*?)\s+-\s+(.*?)(?:\s+-\s+([^-\n]*))?(?:\s+-\s+(.*))?$/).exec(t.title);e?([s,i,n]=e.slice(1,4).map((t=>t?.trim())),this.currentStationData[this.stationName].flipMeta&&([i,s]=[s,i])):console.log("No match found")}if(this.currentStationData[this.stationName].stringPath){const e=(this.currentStationData[this.stationName].pathRegex||/^(.*?)\s+-\s+(.*?)(?:\s+-\s+([^-\n]*))?(?:\s+-\s+(.*))?$/).exec(t);e?(i=e[1]?.trim()||"",s=e[2]?.trim()||"",n=e[3]?.trim()||"",r=e[4]?.trim()||urlCoverArt,this.currentStationData[this.stationName].flipMeta&&([i,s]=[s,i])):console.log("No match found")}s=this.cleanupArtist(s),i=this.applyFilters("track",i).replace(/\s*\(.*?version.*?\)/gi,"").replace(/\s*\(.*?edit.*?\)/gi,"").replace(/[\(\[]\d{4}\s*Mix[\)\]]/i,"").trim(),s=this.applyFilters("artist",s),n=this.applyFilters("album",n);const o=(t,e)=>!!t&&e.some((e=>t.includes(e))),l=t=>{const e=this.currentStationData[this.stationName].filter||[],a=this.currentStationData[this.stationName].stationName;return o(t,e)||o(t,[a])};return l(i)||l(s)?["Station may be taking a break",null,null,urlCoverArt]:i||s?/single/i.exec(n)?[i,s,i,r]:(r=r||urlCoverArt,[i,s,n,r]):["Station data is currently missing",null,null,urlCoverArt]}getLfmMeta(t,e,a){return new Promise(((i,s)=>{if(""!==t&&""!==e){let n="",r="",o="";a?(n="album.getInfo",r="album",o=a):(n="track.getInfo",r="track",o=t);const l=`https://ws.audioscrobbler.com/2.0/?method=${n}&artist=${encodeURIComponent(this.applyFilters("artist",e))}&${r}=${encodeURIComponent(this.applyFilters(r,o))}&api_key=09498b5daf0eceeacbcdc8c6a4c01ccb&autocorrect=1&format=json&limit=1`;fetch(l).then((t=>t.json())).then((r=>{let o="",l="",c="",h="",u=null,d=null;if(6!==r.error)a?(o=r.album?.image[3]["#text"]||urlCoverArt,l=this.applyFilters("album",r.album?.name)||a||"",c=t||"No streaming data currently available",h=this.applyFilters("artist",r.album?.artist)||e||"",u=r.album.listeners||null,d=r.album.playcount||null):(o=r.track?.album?.image[3]["#text"]||urlCoverArt,l=this.applyFilters("album",r.track?.album?.title)||a||"",c=this.applyFilters("track",r.track?.name)||t||"No streaming data currently available",h=this.applyFilters("artist",r.track?.artist?.name)||e||"",u=r.track.listeners||null,d=r.track.playcount||null);else{if("album.getInfo"===n)return void this.getLfmMeta(t,e,"").then(i).catch(s);o=urlCoverArt,l=a||"",c=t||"No streaming data currently available",h=e||"",u=null,d=null}i([o,l,c,h,u,d])})).catch((t=>{console.error("Error fetching Last.fm metadata:",t),s(t)}))}else i(null)}))}getStreamingData(){if(this.isPlaying||null==this.isPlaying){if(!this.stationName)return;if(this.isPlaying&&!this.shouldReloadStream){let t=this.addCacheBuster(this.currentStationData[this.stationName].apiUrl);const e={method:this.currentStationData[this.stationName].method||"GET",headers:this.currentStationData[this.stationName].headers||{}};fetch(t,e).then((t=>{const e=t.headers.get("content-type");if(e.includes("application/json")||e.includes("application/vnd.api+json"))return t.json().then((t=>({data:t,contentType:e})));if(e.includes("text/html")||e.includes("application/javascript"))return t.text().then((t=>({data:t,contentType:e})));throw new Error(`Unsupported content type: ${e}`)})).then((({data:t,contentType:e})=>{if(e.includes("text/html")&&!this.currentStationData[this.stationName].phpString){const e=(new DOMParser).parseFromString(t,"text/html");t=this.extractDataFromHTML(e)}else if(e.includes("application/javascript")){const e=this.extractHTMLFromJS(t),a=(new DOMParser).parseFromString(e,"text/html");t=this.extractDataFromHTML(a)}else e.includes("text/html")&&this.currentStationData[this.stationName].phpString&&console.log("data",t);this.isDataSameAsPrevious(t)||(this.previousDataResponse=t,this.processData(t))})).catch((t=>{console.error("Error fetching streaming data:",t)}))}}}extractHTMLFromJS(t){const e=t.match(/_spinitron\d+\("(.+)"\);/s);if(e&&e[1])return e[1].replace(/\\"/g,'"');throw new Error("Unable to extract HTML content from JavaScript response")}extractDataFromHTML(t){const e=t=>t.replace(/-/g,"—");return`${e(t.querySelector("span.song")?.textContent.trim()||"No streaming data currently available")} - ${e(t.querySelector("span.artist")?.textContent.trim()||"")} - ${e(t.querySelector("span.release")?.textContent.trim()||"")} - ${t.querySelector("img")?.src||""}`}isDataSameAsPrevious(t){return JSON.stringify(t)===JSON.stringify(this.previousDataResponse)}processData(t){if(t&&this.stationName){const e=this.extractSongAndArtist(t,this.stationName);if(!e||0===e.length){return void new Page(this.stationName,this).refreshCurrentData(["No streaming data to show","","",urlCoverArt,null,null,!0,this.currentStationData])}this.hasLoadedData=!0;const[a,i,s,n]=e;let r="";const o=(new Date).getTime();let l,c=this.getPath(t,this.currentStationData[this.stationName].timestamp)||this.getPath(t,this.currentStationData[this.stationName].timestamp2)||"";if(String(c).includes("T"))l=Date.parse(c);else if(String(c).match(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/)){const[t,e]=c.split(" "),[a,i,s]=t.split("-"),[n,r,o]=e.split(":");l=new Date(a,i-1,s,n,r,o).getTime()}else l=1e3*parseInt(c);if(o-l>9e5&&""!==c&&(r="Streaming data is stale"),"No streaming data currently available"===a||"Station may be taking a break"===a||"Station data is currently missing"===a||r){return void new Page(this.stationName,this).refreshCurrentData([r||a,"","",urlCoverArt,null,null,!0,this.currentStationData])}if(this.isDataSameAsPrevious(t)&&this.lfmMetaChanged&&a===this.song)return;this.lfmMetaChanged&&a.toLowerCase()===this.song.toLowerCase()||this.getLfmMeta(a,i,s).then((e=>{const[s,r,o,l,c,h]=e||[urlCoverArt,"",a,i,"",""];this.song=o,this.artist=l,this.album=r,this.artworkUrl=s===urlCoverArt?this.upsizeImgUrl(n)||this.upsizeImgUrl(this.getPath(t,this.currentStationData[this.stationName].albumArt))||urlCoverArt:this.upsizeImgUrl(s),this.listeners=c,this.playcount=h,this.lfmMetaChanged=!0;new Page(this.stationName,this).refreshCurrentData([this.song,this.artist,this.album,this.artworkUrl,this.listeners,this.playcount,!0,this.currentStationData])})).catch((t=>{console.error("Error processing data:",t)}))}}upsizeImgUrl(t){return t?t.replace(/170x170|360x360|300x300/g,"500x500"):void 0}getPath(t,e){if(t&&"object"==typeof t&&e){if(!0!==this.currentStationData[this.stationName].needPath)return t[e];for(var a=e.split("."),i=a.pop(),s=a.length,n=1,r=a[0];(t=t[r])&&n{this.isPlaying=!0,this.playButton.lastElementChild.className="icon-pause",document.getElementById("metadata").classList.add("playing"),this.pauseTimeout&&(clearTimeout(this.pauseTimeout),this.pauseTimeout=null)})).catch((t=>{console.error("Error playing audio:",t)})))}pause(){this.audio.pause(),this.isPlaying=!1,this.playButton.lastElementChild.className="icon-play",document.getElementById("metadata").classList.remove("playing"),this.pauseTimeout&&clearTimeout(this.pauseTimeout),this.pauseTimeout=setTimeout((()=>{console.log("the stream should be reloaded"),this.shouldReloadStream=!0}),3e4)}togglePlay(){this.isPlaying?this.pause():this.play()}skipToNextStation(){this.calculateNextAndPreviousIndices();const t=stationKeys[this.nextIndex];this.handleStationSelect(null,t,!0)}skipBackward(){this.playButton.lastElementChild.className="spinner-grow text-light",this.calculateNextAndPreviousIndices();const t=stationKeys[this.previousIndex];this.handleStationSelect(!0,t,!0)}skipForward(){this.playButton.lastElementChild.className="spinner-grow text-light",this.calculateNextAndPreviousIndices();const t=stationKeys[this.nextIndex];this.handleStationSelect(null,t,!0)}reloadStream(){this.shouldReloadStream=!0,console.log("reload working"),this.playButton.lastElementChild.className="spinner-grow text-light",this.calculateNextAndPreviousIndices();const t=stationKeys[this.currentIndex];this.handleStationSelect(!0,t,!0)}addCacheBuster(t){const e=(new Date).getTime();return"radiowestern"===this.stationName?t:t.includes("?")?`${t}&t=${e}`:`${t}?t=${e}`}}const radioPlayer=new RadioPlayer(document.getElementById("playButton"),document.getElementById("skipForward"),document.getElementById("skipBack"),document.getElementById("reloadStream"));document.addEventListener("DOMContentLoaded",(async function(){await generateRadioButtons(),radioPlayer.jumpToStationFromHash();const t=stationKeys[0];radioPlayer.handleStationSelect(!1,t,!0);const e=document.getElementById("stationSelect");e?e.addEventListener("change",(t=>{const e=t.target.value;radioPlayer.handleStationSelect(!0,e,!0)}),{once:!0}):console.error('Element with ID "stationSelect" not found.');const a=document.querySelector(".mobile-swipe"),i=document.getElementById("panel2");a&&i&&a.scrollTo({left:i.offsetLeft,behavior:"smooth"})}),{once:!0}); \ No newline at end of file +const urlCoverArt="img/defaultArt.png",stationKeys=Object.keys(stations);let skipCORS="";async function generateRadioButtons(){skipCORS=await isCORSEnabled("https://scrobblerad.io/");const t=document.getElementById("stationSelect");t.innerHTML="";const e=document.createDocumentFragment();stationKeys.filter((t=>{const e=stations[t];return!(!1===skipCORS&&e.cors)})).forEach((t=>{const a=stations[t],i=document.createElement("button");i.name=t,i.textContent=a.stationName;const s=document.createElement("input");s.type="radio",s.name="station",s.value=t,s.checked=t===radioPlayer.stationName,i.appendChild(s),e.appendChild(i)})),t.appendChild(e),t.addEventListener("click",(t=>{const e=t.target.closest("button");if(e){t.preventDefault();const a=e.name;window.location.hash=`#${a}`,radioPlayer.handleStationSelect(null,a,!0)}})),document.getElementById("togglePanels").addEventListener("click",(function(){const t=document.getElementById("panel1"),e=document.getElementById("panel2"),a=document.getElementById("panel3"),i=document.querySelector("#togglePanels .icon-hide-panels, #togglePanels .icon-show-panels");t.classList.toggle("show"),e.classList.toggle("grow"),a.classList.toggle("show"),i&&(i.classList.contains("icon-hide-panels")?(i.classList.remove("icon-hide-panels"),i.classList.add("icon-show-panels")):(i.classList.remove("icon-show-panels"),i.classList.add("icon-hide-panels")))}))}async function isCORSEnabled(t){try{return!!(await fetch(t,{method:"GET",mode:"cors"})).ok}catch(t){return"TypeError"===t.name&&t.message.includes("Failed to fetch"),!1}}function animateElement(t,e=2e3){t.classList.add("animated","fadeIn"),setTimeout((()=>{t.classList.remove("animated","fadeIn")}),e)}class Page{constructor(t,e){this.stationName=t,this.displayStationName=stations[t].stationName,this.radioPlayer=e,this.cacheDOMElements(),this.setupMediaSession("","",""),this.template=document.querySelector("#meta")}cacheDOMElements(){const t={currentSong:"title",currentArtist:"artist",currentAlbum:"album",currentListeners:"listeners",coverArt:"albumArt",radioNameLink:"radioNameLink",radioName:"radioName",stationLocation:"stationLocation",metaInfo:"metainfo"};for(const[e,a]of Object.entries(t))this[e+"Element"]=document.getElementById(a)}formatCompactNumber(t){return t<1e3?t:t>=1e3&&t<1e6?(t/1e3).toFixed(1).replace(/\.0$/,"")+"K":t>=1e6&&t<1e9?(t/1e6).toFixed(1).replace(/\.0$/,"")+"M":t>=1e9&&t<1e12?(t/1e9).toFixed(1).replace(/\.0$/,"")+"B":void 0}refreshCurrentData(t){const[e,a,i,s,n,r,,o]=t;setTimeout((()=>{const t=()=>{if(!e&&!a||!s||!o[this.stationName])return;const t=document.querySelector("div.playermeta");t.textContent="";const l=document.querySelector("#meta"),c=document.importNode(l.content,!0);c.querySelector("#title").textContent=e,c.querySelector("#artist").textContent=a,c.querySelector("#album").textContent=i,c.querySelector("#listeners").textContent=null!==n&&null!==r?`Listeners: ${this.formatCompactNumber(n)} | Plays: ${this.formatCompactNumber(r)}`:"",c.querySelector("#albumArt").src=s,c.querySelector("#albumArt").alt=`${e} by ${a}`;c.querySelector("#radioNameLink").href=o[this.stationName].webUrl,c.querySelector("#radioName").textContent=o[this.stationName].stationName,c.querySelector("#stationLocation").textContent=o[this.stationName].location,t.appendChild(c);const h=s===urlCoverArt?`url("../${s}")`:`url("${s}")`;document.documentElement.style.setProperty("--albumArt",h),animateElement(t),document.querySelector("#panel2").click(),this.setupMediaSession(e,a,s)},l=new Image;l.onload=()=>{setTimeout(t,0)},l.src=s}),1500)}setupMediaSession(t,e,a){if("mediaSession"in navigator){navigator.mediaSession.metadata=new MediaMetadata({title:t,artist:e||"",album:`Now playing on ${this.displayStationName}`||"",duration:1/0,startTime:0,artwork:[{src:a}]}),t&&e&&(document.title=`${t} - ${e} | ${this.displayStationName} on scrobblerad.io`);const i={nexttrack:()=>this.radioPlayer.skipForward(),previoustrack:()=>this.radioPlayer.skipBackward(),play:()=>this.radioPlayer.togglePlay(),pause:()=>this.radioPlayer.togglePlay()};for(const[t,e]of Object.entries(i))navigator.mediaSession.setActionHandler(t,e)}}}class RadioPlayer{constructor(t,e,a,i){this.currentStationData=null,this.audio=new Audio,this.playButton=t,this.skipForwardButton=e,this.skipBackButton=a,this.reloadStreamButton=i,this.isPlaying=null,this.stationName="",this.previousDataResponse=null,this.pauseTimeout=null,this.shouldReloadStream=!1,this.stations=document.querySelectorAll(".station"),this.debounceTimeout=null,this.firstRun=!0,this.streamingInterval=null,this.canAutoplay=!1,this.debouncedPlayAudio=this.debounce((t=>{this.audio&&(this.audio.pause(),this.audio=null),setTimeout((()=>{this.audio=t,this.getStreamingData(),this.play(),this.isPlaying=!0}),500)}),1500),this.bindMethods(),this.addEventListeners(),this.init()}bindMethods(){this.handleStationSelect=this.handleStationSelect.bind(this),this.getLfmMeta=this.getLfmMeta.bind(this),this.getStreamingData=this.getStreamingData.bind(this),this.extractSongAndArtist=this.extractSongAndArtist.bind(this),this.getPath=this.getPath.bind(this),this.upsizeImgUrl=this.upsizeImgUrl.bind(this),this.togglePlay=this.togglePlay.bind(this),this.skipForward=this.skipForward.bind(this),this.skipBackward=this.skipBackward.bind(this),this.reloadStream=this.reloadStream.bind(this)}async addEventListeners(){this.playButton.addEventListener("click",this.togglePlay),this.skipForwardButton.addEventListener("click",this.skipForward),this.skipBackButton.addEventListener("click",this.skipBackward),this.reloadStreamButton.addEventListener("click",this.reloadStream),document.getElementById("stationSelect").addEventListener("click",(t=>{t.target&&t.target.matches("input[name='station']")&&(this.handleStationSelect(t,t.target.value,!0),t.target.scrollIntoView({behavior:"smooth",block:"nearest"}))})),this.jumpToStationFromHash(),document.addEventListener("DOMContentLoaded",(()=>{this.jumpToStationFromHash()}),{once:!0})}init(){"serviceWorker"in navigator&&document.addEventListener("DOMContentLoaded",(()=>{navigator.serviceWorker.register("serviceWorker.js").then((()=>console.log("Service worker registered"))).catch((t=>console.log("Service worker not registered",t)))}),{once:!0})}calculateNextAndPreviousIndices(){this.currentIndex=stationKeys.indexOf(this.stationName);const t=stationKeys[(this.currentIndex+1)%stationKeys.length],e=stationKeys[(this.currentIndex-1+stationKeys.length)%stationKeys.length],a=stations[t].cors&&!skipCORS?2:1,i=stations[e].cors&&!skipCORS?2:1;this.nextIndex=(this.currentIndex+a)%stationKeys.length,this.previousIndex=(this.currentIndex-i+stationKeys.length)%stationKeys.length}debounce(t,e){return(...a)=>{this.debounceTimeout&&clearTimeout(this.debounceTimeout),this.debounceTimeout=setTimeout((()=>{t.apply(this,a)}),e)}}jumpToStationFromHash(){const t=window.location.hash;if(t){const e=t.substring(1),a=document.querySelector(`button[name='${e}']`);a&&(a.scrollIntoView({behavior:"smooth",block:"center"}),this.handleStationSelect(null,e,!0))}}async loadStationData(t){try{const e=await fetch(t),a=await e.text();return new Function(a+"; return stationData;")()}catch(t){console.error("Error loading station data:",t)}}async handleStationSelect(t,e,a){if(!e||!1===t)return;const i=await this.loadStationData(`/js/stations/${e}.js`);if(!i)return;this.currentStationData=i,stations[e].stationName===this.currentStationData[e].stationName&&(document.title=`${this.currentStationData[e].stationName} currently loading`),this.streamingInterval&&(clearInterval(this.streamingInterval),this.streamingInterval=null),a&&(this.playButton.lastElementChild.className="spinner-grow text-light",this.lfmMetaChanged=!1,console.log(e),this.stationName=e,this.updateArt=!0,this.isPlaying=!0,a=!1);this.debounce((()=>{if(!this.isPlaying)return;if(!this.currentStationData[this.stationName])return void console.error("currentStationData is undefined or null");this.currentStationData[this.stationName].stationName;const a=new Audio(this.addCacheBuster(this.currentStationData[this.stationName].streamUrl));a.onloadedmetadata=()=>{this.lfmMetaChanged=!1,this.debouncedPlayAudio(a),this.streamingInterval=setInterval((()=>{this.getStreamingData()}),25e3)},a.onerror=e=>{console.warn("Error loading audio:",e),this.isPlaying&&(!0===t?this.skipBackward():this.skipForward())},a.load();new Page(this.stationName,this);const i=document.querySelector(`input[name='station'][value='${e}']`);i&&(i.checked=!0),window.location.hash=`#${e}`}),250)()}cleanupArtist(t){let e=t;return[/ x .*/,/ feat\..*/].forEach((t=>{e=e.replace(t,"")})),e.trim()}getFilterSet(){return{artist:[MetadataFilter.normalizeFeature],track:[MetadataFilter.removeRemastered,MetadataFilter.removeFeature,MetadataFilter.removeLive,MetadataFilter.removeCleanExplicit,MetadataFilter.removeVersion,MetadataFilter.youtube],album:[MetadataFilter.removeRemastered,MetadataFilter.removeFeature,MetadataFilter.removeLive,MetadataFilter.removeCleanExplicit,MetadataFilter.removeVersion]}}applyFilters(t,e){return MetadataFilter.createFilter(this.getFilterSet()).filterField(t,e)}extractSongAndArtist(t,e){const a=e=>{return a=this.getPath(t,this.currentStationData[this.stationName][e]),a?.replace(/'|’|‘|‚|‛|`|´/g,"'")||"";var a};let i=a("song"),s=a("artist"),n=a("album"),r=a("albumArt");if(this.currentStationData[this.stationName].altPath&&!i&&(i=a("song2"),s=a("artist2")),this.currentStationData[this.stationName].orbPath){const e=(this.currentStationData[this.stationName].pathRegex||/^(.*?)\s+-\s+(.*?)(?:\s+-\s+([^-\n]*))?(?:\s+-\s+(.*))?$/).exec(t.title);e?([s,i,n]=e.slice(1,4).map((t=>t?.trim())),this.currentStationData[this.stationName].flipMeta&&([i,s]=[s,i])):console.log("No match found")}if(this.currentStationData[this.stationName].stringPath){const e=(this.currentStationData[this.stationName].pathRegex||/^(.*?)\s+-\s+(.*?)(?:\s+-\s+([^-\n]*))?(?:\s+-\s+(.*))?$/).exec(t);e?(i=e[1]?.trim()||"",s=e[2]?.trim()||"",n=e[3]?.trim()||"",r=e[4]?.trim()||urlCoverArt,this.currentStationData[this.stationName].flipMeta&&([i,s]=[s,i])):console.log("No match found")}s=this.cleanupArtist(s),i=this.applyFilters("track",i).replace(/\s*\(.*?version.*?\)/gi,"").replace(/\s*\(.*?edit.*?\)/gi,"").replace(/[\(\[]\d{4}\s*Mix[\)\]]/i,"").trim(),s=this.applyFilters("artist",s),n=this.applyFilters("album",n);const o=(t,e)=>!!t&&e.some((e=>t.includes(e))),l=t=>{const e=this.currentStationData[this.stationName].filter||[],a=this.currentStationData[this.stationName].stationName;return o(t,e)||o(t,[a])};return l(i)||l(s)?["Station may be taking a break",null,null,urlCoverArt]:i||s?/single/i.exec(n)?[i,s,i,r]:(r=r||urlCoverArt,[i,s,n,r]):["Station data is currently missing",null,null,urlCoverArt]}getLfmMeta(t,e,a){return new Promise(((i,s)=>{if(""!==t&&""!==e){let n="",r="",o="";a?(n="album.getInfo",r="album",o=a):(n="track.getInfo",r="track",o=t);const l=`https://ws.audioscrobbler.com/2.0/?method=${n}&artist=${encodeURIComponent(this.applyFilters("artist",e))}&${r}=${encodeURIComponent(this.applyFilters(r,o))}&api_key=09498b5daf0eceeacbcdc8c6a4c01ccb&autocorrect=1&format=json&limit=1`;fetch(l).then((t=>t.json())).then((r=>{let o="",l="",c="",h="",u=null,d=null;if(6!==r.error)a?(o=r.album?.image[3]["#text"]||urlCoverArt,l=this.applyFilters("album",r.album?.name)||a||"",c=t||"No streaming data currently available",h=this.applyFilters("artist",r.album?.artist)||e||"",u=r.album.listeners||null,d=r.album.playcount||null):(o=r.track?.album?.image[3]["#text"]||urlCoverArt,l=this.applyFilters("album",r.track?.album?.title)||a||"",c=this.applyFilters("track",r.track?.name)||t||"No streaming data currently available",h=this.applyFilters("artist",r.track?.artist?.name)||e||"",u=r.track.listeners||null,d=r.track.playcount||null);else{if("album.getInfo"===n)return void this.getLfmMeta(t,e,"").then(i).catch(s);o=urlCoverArt,l=a||"",c=t||"No streaming data currently available",h=e||"",u=null,d=null}i([o,l,c,h,u,d])})).catch((t=>{console.error("Error fetching Last.fm metadata:",t),s(t)}))}else i(null)}))}getStreamingData(){if(this.isPlaying||null==this.isPlaying){if(!this.stationName)return;if(this.isPlaying&&!this.shouldReloadStream){let t=this.addCacheBuster(this.currentStationData[this.stationName].apiUrl);const e={method:this.currentStationData[this.stationName].method||"GET",headers:this.currentStationData[this.stationName].headers||{}};fetch(t,e).then((t=>{const e=t.headers.get("content-type");if(e.includes("application/json")||e.includes("application/vnd.api+json"))return t.json().then((t=>({data:t,contentType:e})));if(e.includes("text/html")||e.includes("application/javascript"))return t.text().then((t=>({data:t,contentType:e})));throw new Error(`Unsupported content type: ${e}`)})).then((({data:t,contentType:e})=>{if(e.includes("text/html")&&!this.currentStationData[this.stationName].phpString){const e=(new DOMParser).parseFromString(t,"text/html");t=this.extractDataFromHTML(e)}else if(e.includes("application/javascript")){const e=this.extractHTMLFromJS(t),a=(new DOMParser).parseFromString(e,"text/html");t=this.extractDataFromHTML(a)}else e.includes("text/html")&&this.currentStationData[this.stationName].phpString&&console.log("data",t);this.isDataSameAsPrevious(t)||(this.previousDataResponse=t,this.processData(t))})).catch((t=>{console.error("Error fetching streaming data:",t)}))}}}extractHTMLFromJS(t){const e=t.match(/_spinitron\d+\("(.+)"\);/s);if(e&&e[1])return e[1].replace(/\\"/g,'"');throw new Error("Unable to extract HTML content from JavaScript response")}extractDataFromHTML(t){const e=t=>t.replace(/-/g,"—");return`${e(t.querySelector("span.song")?.textContent.trim()||"No streaming data currently available")} - ${e(t.querySelector("span.artist")?.textContent.trim()||"")} - ${e(t.querySelector("span.release")?.textContent.trim()||"")} - ${t.querySelector("img")?.src||""}`}isDataSameAsPrevious(t){return JSON.stringify(t)===JSON.stringify(this.previousDataResponse)}processData(t){if(t&&this.stationName){const e=this.extractSongAndArtist(t,this.stationName);if(!e||0===e.length){return void new Page(this.stationName,this).refreshCurrentData(["No streaming data to show","","",urlCoverArt,null,null,!0,this.currentStationData])}this.hasLoadedData=!0;const[a,i,s,n]=e;let r="";const o=(new Date).getTime();let l,c=this.getPath(t,this.currentStationData[this.stationName].timestamp)||this.getPath(t,this.currentStationData[this.stationName].timestamp2)||"";if(String(c).includes("T"))l=Date.parse(c);else if(String(c).match(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/)){const[t,e]=c.split(" "),[a,i,s]=t.split("-"),[n,r,o]=e.split(":");l=new Date(a,i-1,s,n,r,o).getTime()}else l=1e3*parseInt(c);if(o-l>9e5&&""!==c&&(r="Streaming data is stale"),"No streaming data currently available"===a||"Station may be taking a break"===a||"Station data is currently missing"===a||r){return void new Page(this.stationName,this).refreshCurrentData([r||a,"","",urlCoverArt,null,null,!0,this.currentStationData])}if(this.isDataSameAsPrevious(t)&&this.lfmMetaChanged&&a===this.song)return;this.lfmMetaChanged&&a.toLowerCase()===this.song.toLowerCase()||this.getLfmMeta(a,i,s).then((e=>{const[s,r,o,l,c,h]=e||[urlCoverArt,"",a,i,"",""];this.song=o,this.artist=l,this.album=r,this.artworkUrl=s===urlCoverArt?this.upsizeImgUrl(n)||this.upsizeImgUrl(this.getPath(t,this.currentStationData[this.stationName].albumArt))||urlCoverArt:this.upsizeImgUrl(s),this.listeners=c,this.playcount=h,this.lfmMetaChanged=!0;new Page(this.stationName,this).refreshCurrentData([this.song,this.artist,this.album,this.artworkUrl,this.listeners,this.playcount,!0,this.currentStationData])})).catch((t=>{console.error("Error processing data:",t)}))}}upsizeImgUrl(t){return t?t.replace(/170x170|360x360|300x300/g,"500x500"):void 0}getPath(t,e){if(t&&"object"==typeof t&&e){if(!0!==this.currentStationData[this.stationName].needPath)return t[e];for(var a=e.split("."),i=a.pop(),s=a.length,n=1,r=a[0];(t=t[r])&&n{this.isPlaying=!0,this.playButton.lastElementChild.className="icon-pause",document.getElementById("metadata").classList.add("playing"),this.pauseTimeout&&(clearTimeout(this.pauseTimeout),this.pauseTimeout=null)})).catch((t=>{console.error("Error playing audio:",t)})))}pause(){this.audio.pause(),this.isPlaying=!1,this.playButton.lastElementChild.className="icon-play",document.getElementById("metadata").classList.remove("playing"),this.pauseTimeout&&clearTimeout(this.pauseTimeout),this.pauseTimeout=setTimeout((()=>{console.log("the stream should be reloaded"),this.shouldReloadStream=!0}),3e4)}togglePlay(){this.isPlaying?this.pause():this.play()}skipToNextStation(){this.calculateNextAndPreviousIndices();const t=stationKeys[this.nextIndex];this.handleStationSelect(null,t,!0)}skipBackward(){this.playButton.lastElementChild.className="spinner-grow text-light",this.calculateNextAndPreviousIndices();const t=stationKeys[this.previousIndex];this.handleStationSelect(!0,t,!0)}skipForward(){this.playButton.lastElementChild.className="spinner-grow text-light",this.calculateNextAndPreviousIndices();const t=stationKeys[this.nextIndex];this.handleStationSelect(null,t,!0)}reloadStream(){this.shouldReloadStream=!0,console.log("reload working"),this.playButton.lastElementChild.className="spinner-grow text-light",this.calculateNextAndPreviousIndices();const t=stationKeys[this.currentIndex];this.handleStationSelect(!0,t,!0)}addCacheBuster(t){const e=(new Date).getTime();return"radiowestern"===this.stationName?t:t.includes("?")?`${t}&t=${e}`:`${t}?t=${e}`}}const radioPlayer=new RadioPlayer(document.getElementById("playButton"),document.getElementById("skipForward"),document.getElementById("skipBack"),document.getElementById("reloadStream"));document.addEventListener("DOMContentLoaded",(async function(){await generateRadioButtons(),radioPlayer.jumpToStationFromHash();const t=stationKeys[0];radioPlayer.handleStationSelect(!1,t,!0);const e=document.getElementById("stationSelect");e?e.addEventListener("change",(t=>{const e=t.target.value;radioPlayer.handleStationSelect(!0,e,!0)}),{once:!0}):console.error('Element with ID "stationSelect" not found.');const a=document.querySelector(".mobile-swipe"),i=document.getElementById("panel2");a&&i&&a.scrollTo({left:i.offsetLeft,behavior:"smooth"})}),{once:!0}); \ No newline at end of file diff --git a/js/main.js b/js/main.js index 43ddf6f..a39b202 100644 --- a/js/main.js +++ b/js/main.js @@ -381,7 +381,9 @@ class RadioPlayer { this.currentStationData = stationData; - document.title = `${this.currentStationData[stationName].stationName} currently loading`; + if (stations[stationName].stationName === this.currentStationData[stationName].stationName) { + document.title = `${this.currentStationData[stationName].stationName} currently loading`; + } // Clear any existing streaming intervals if (this.streamingInterval) {