nixamp 0.21.2 → 0.21.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/web/dist/assets/{hls-3VKVEQE3-D3aTmGbF.js → hls-3VKVEQE3-BIt1wpN9.js} +1 -1
- package/web/dist/assets/index-CqVKzhBv.js +1 -0
- package/web/dist/assets/{mpegts-CnrnWJiy.js → mpegts-DmA-kgnY.js} +1 -1
- package/web/dist/assets/{mpegts-LO6RVLD6-DRR1aiJm.js → mpegts-LO6RVLD6-Dtn6km2F.js} +1 -1
- package/web/dist/index.html +1 -1
- package/web/dist/sw.js +5 -5
- package/web/dist/assets/index-D2Ir3z0h.js +0 -1
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{t as e}from"./index-
|
|
1
|
+
import{t as e}from"./index-CqVKzhBv.js";var t=3;async function n(n){let{media:r,src:i,isTv:a}=n,{default:o}=await e(async()=>{let{default:e}=await import(`./hls-n74Cnh8A.js`);return{default:e}},[]);if(!o.isSupported())return n.onError(`This browser cannot play HLS streams.`),{destroy:()=>void 0,levels:()=>[]};let s=new o({...a?{maxBufferLength:60,maxMaxBufferLength:120,backBufferLength:30,liveSyncDurationCount:4}:{backBufferLength:90},enableWorker:!0}),c=0,l=!1;s.on(o.Events.ERROR,(e,r)=>{if(!l&&r.fatal){if(c>=t){n.onError(`This stream kept failing and has been stopped.`),s.destroy();return}switch(c+=1,r.type){case o.ErrorTypes.NETWORK_ERROR:n.onNotice(`Reconnecting…`),s.startLoad();break;case o.ErrorTypes.MEDIA_ERROR:n.onNotice(`Recovering…`),s.recoverMediaError();break;default:n.onError(`This stream could not be played.`),s.destroy()}}}),s.on(o.Events.MANIFEST_PARSED,()=>{l||(n.onNotice(null),n.onReady?.({live:s.levels.length>0&&!Number.isFinite(r.duration),levels:u()}))}),s.on(o.Events.LEVEL_LOADED,(e,t)=>{l||n.onReady?.({live:t.details.live,levels:u()})}),s.on(o.Events.FRAG_BUFFERED,()=>{l||n.onNotice(null)});function u(){return s.levels.map((e,t)=>({index:t,height:e.height||null,bitrate:e.bitrate||null,label:e.height?`${String(e.height)}p`:`${String(Math.round((e.bitrate||0)/1e3))}k`}))}return s.loadSource(i),s.attachMedia(r),{destroy(){l=!0,s.destroy()},levels:u,setLevel(e){s.currentLevel=e},currentLevel:()=>s.autoLevelEnabled?-1:s.currentLevel}}export{n as createHlsEngine};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();function e(e){if(!Number.isFinite(e)||e<0)return`--:--`;let t=Math.floor(e),n=Math.floor(t/60),r=t%60;return`${String(n).padStart(2,`0`)}:${String(r).padStart(2,`0`)}`}function t(e){return e.artist?`${e.artist} — ${e.title}`:e.title}function n(e){let t=e.split(`/`).pop()??e,n=t.lastIndexOf(`.`);return n>0?t.slice(0,n):t}var r=new Set([`mp4`,`webm`,`mkv`,`mov`,`m4v`,`ogv`,`avi`,`ts`,`m2ts`,`mts`,`m2t`,`trp`,`tp`]);function i(e,t=``){if(t.startsWith(`video/`))return!0;if(t.startsWith(`audio/`))return!1;let n=e.lastIndexOf(`.`);return n>0&&r.has(e.slice(n+1).toLowerCase())}var a=`modulepreload`,o=function(e){return`/`+e},s={},c=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e=document.getElementsByTagName(`link`),i=document.querySelector(`meta[property=csp-nonce]`),c=i?.nonce||i?.getAttribute(`nonce`);function l(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}function u(e){return import.meta.resolve?import.meta.resolve(e):new URL(e,import.meta.url).href}r=l(t.map(t=>{if(t=o(t,n),t=u(t),t in s)return;s[t]=!0;let r=t.endsWith(`.css`);for(let n=e.length-1;n>=0;n--){let i=e[n];if(i.href===t&&(!r||i.rel===`stylesheet`))return}let i=document.createElement(`link`);if(i.rel=r?`stylesheet`:a,r||(i.as=`script`),i.crossOrigin=``,i.href=t,c&&i.setAttribute(`nonce`,c),document.head.appendChild(i),r)return new Promise((e,n)=>{i.addEventListener(`load`,e),i.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${t}`)))})}))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})},l=[[/\.m3u8$/i,`hls`],[/\.(ts|mts|m2ts|mpegts)$/i,`mpegts`],[/\.(mp3|m4a|aac|oga|ogg|opus|wav|flac)$/i,`audio`],[/\.(mp4|m4v|webm|mov|ogv)$/i,`mp4`]],u=[[/mpegurl/i,`hls`],[/mp2t|mpeg-?ts/i,`mpegts`],[/^audio\//i,`audio`],[/^video\//i,`mp4`]];function d(e){if(e.kind)return e.kind;if(e.mimeType){for(let[t,n]of u)if(t.test(e.mimeType))return n}let t=f(e.src);for(let[e,n]of l)if(e.test(t))return n;return`unknown`}function f(e){try{return new URL(e,`https://placeholder.invalid`).pathname}catch{return e.split(/[?#]/)[0]??e}}function p(e=globalThis,t=null){let n=e.MediaSource!==void 0,r=!1;try{let e=t??(typeof document>`u`?null:document.createElement(`video`));r=e?e.canPlayType(`application/vnd.apple.mpegurl`)!==``||e.canPlayType(`application/x-mpegURL`)!==``:!1}catch{r=!1}return{mediaSource:n,nativeHls:r}}function m(e,t){let n=d(e);switch(n){case`hls`:return t.mediaSource?{engine:`hls`,kind:n}:t.nativeHls?{engine:`native`,kind:n}:{engine:`native`,kind:n,unplayable:`This browser cannot play HLS streams.`};case`mpegts`:return t.mediaSource?{engine:`mpegts`,kind:n}:{engine:`mpegts`,kind:n,unplayable:`This browser cannot play transport streams.`};default:return{engine:`native`,kind:n}}}async function h(e,t){let n=t.capabilities??p(),r=m({src:t.src,...t.kind?{kind:t.kind}:{},...t.mimeType?{mimeType:t.mimeType}:{}},n),i=()=>void 0,a={media:e,src:t.src,isTv:t.isTv??!1,live:t.live??r.kind===`mpegts`,onError:t.onError??i,onNotice:t.onNotice??i,...t.onReady?{onReady:t.onReady}:{}};if(r.unplayable)return t.onError?.(r.unplayable),{destroy:i,engine:r.engine,kind:r.kind,levels:()=>[],unplayable:r.unplayable};let o,s=t.engines?.[r.engine];if(s)o=await s(a);else if(r.engine===`hls`){let{createHlsEngine:e}=await c(async()=>{let{createHlsEngine:e}=await import(`./hls-3VKVEQE3-BIt1wpN9.js`);return{createHlsEngine:e}},[]);o=await e(a)}else if(r.engine===`mpegts`){let{createMpegtsEngine:e}=await c(async()=>{let{createMpegtsEngine:e}=await import(`./mpegts-LO6RVLD6-Dtn6km2F.js`);return{createMpegtsEngine:e}},[]);o=await e(a,{withCredentials:t.withCredentials??!1,unplayableAdvice:t.unplayableAdvice??``})}else{let{createNativeEngine:e}=await c(async()=>{let{createNativeEngine:e}=await import(`./native-C7JTKWJH-BUyIoj0P.js`);return{createNativeEngine:e}},[]);o=await e(a)}return{destroy:()=>{o.destroy()},engine:r.engine,kind:r.kind,levels:o.levels,...o.setLevel?{setLevel:o.setLevel}:{},...o.currentLevel?{currentLevel:o.currentLevel}:{}}}var ee=new Set([`mp3`,`flac`,`ogg`,`oga`,`opus`,`m4a`,`aac`,`wav`,`wma`,`aiff`,`aif`,`alac`,`mp4`,`webm`,`mkv`,`mov`,`m4v`,`ogv`,`ts`,`m2ts`,`mts`,`m2t`,`trp`,`tp`]),te=new Set([`ts`,`m2ts`,`mts`,`m2t`,`trp`,`tp`]);function g(e){let t=e.lastIndexOf(`.`);return t>0&&te.has(e.slice(t+1).toLowerCase())}function _(e,t=``){if(t.startsWith(`audio/`)||t.startsWith(`video/`))return!0;let n=e.lastIndexOf(`.`);return n>0&&ee.has(e.slice(n+1).toLowerCase())}function ne(e,t){return e.localeCompare(t,void 0,{numeric:!0,sensitivity:`base`})}function re(e){return e.filter(e=>_(e.name,e.type)).sort((e,t)=>ne(ie(e),ie(t))).map(e=>({title:n(e.name),artist:``,album:ae(ie(e)),duration:0,url:URL.createObjectURL(e),video:i(e.name,e.type),objectUrl:!0,...g(e.name)?{kind:`mpegts`}:{}}))}function ie(e){return e.webkitRelativePath||e.name}function ae(e){let t=e.split(`/`);return t.length>1?t[t.length-2]:``}function oe(e){for(let t of e)t.objectUrl&&URL.revokeObjectURL(t.url)}var se=2048;function v(e){if(e.startsWith(`blob:`)||e.startsWith(`data:`))return!0;try{let t=globalThis.location?.origin;return t?new URL(e,t).origin===t:!1}catch{return!1}}function ce(e,t){return e||t===`hls`||t===`mpegts`}var le=class{elements;handlers;attached=null;source=``;context=null;analyser=null;wired=new WeakSet;active;frequencies=new Uint8Array;analysable=!0;constructor(e,t){this.elements=e,this.handlers=t,this.active=e.audio;for(let t of[e.audio,e.video]){t.addEventListener(`timeupdate`,()=>{t===this.active&&this.handlers.onTime(t.currentTime,Number.isFinite(t.duration)?t.duration:0)}),t.addEventListener(`loadedmetadata`,()=>{t===this.active&&this.handlers.onTime(t.currentTime,Number.isFinite(t.duration)?t.duration:0)}),t.addEventListener(`ended`,()=>{t===this.active&&this.handlers.onEnded()}),t.addEventListener(`play`,()=>{t===this.active&&this.handlers.onState(!0)}),t.addEventListener(`pause`,()=>{t===this.active&&this.handlers.onState(!1)}),t.addEventListener(`error`,()=>{t===this.active&&this.handlers.onError(y(t))});let e=e=>()=>{t===this.active&&this.handlers.onBusy?.(e)};for(let n of[`loadstart`,`waiting`,`stalled`,`seeking`])t.addEventListener(n,e(!0));for(let n of[`playing`,`canplay`,`pause`,`ended`,`error`,`emptied`,`seeked`,`abort`])t.addEventListener(n,e(!1))}}get playing(){return!this.active.paused&&!this.active.ended}get position(){return this.active.currentTime}get duration(){return Number.isFinite(this.active.duration)?this.active.duration:0}get showingVideo(){return this.active===this.elements.video}ensureGraph(e){let t=globalThis.AudioContext??globalThis.webkitAudioContext;if(t){if(this.context??=new t,this.analyser||(this.analyser=this.context.createAnalyser(),this.analyser.fftSize=se,this.analyser.smoothingTimeConstant=.6,this.analyser.connect(this.context.destination),this.frequencies=new Uint8Array(this.analyser.frequencyBinCount)),!this.wired.has(e))try{this.context.createMediaElementSource(e).connect(this.analyser),this.wired.add(e)}catch{this.wired.add(e)}this.context.resume()}}read(){return this.analyser&&this.analyser.getByteFrequencyData(this.frequencies),this.frequencies}levels(){if(!this.analyser)return[0,0];let e=this.read(),t=0;for(let n of e)t+=n;let n=e.length===0?0:t/e.length/255;return[Math.min(1,n*2.2),Math.min(1,n*2.2)]}async load(e,t){this.source=e.objectUrl?``:e.url;let n=e.kind??(e.objectUrl?e.video?`mp4`:`audio`:d({src:e.url})),r=ce(e.video,n)?this.elements.video:this.elements.audio,i=e.objectUrl||v(e.url);r.crossOrigin=i?`anonymous`:null,r!==this.active&&(this.active.pause(),this.active.removeAttribute(`src`),this.active.load(),this.active=r),this.attached?.destroy(),this.attached=null;try{this.attached=await h(this.active,{src:e.url,kind:n,unplayableAdvice:`VLC or mpv will play it; nixamp can only hand it to your browser.`,onError:e=>this.handlers.onError(e),onNotice:e=>{e&&this.handlers.onError(e)}})}catch(e){this.handlers.onError(e instanceof Error?e.message:`that would not play`);return}let a=this.active.currentSrc||this.active.src||e.url;this.analysable=i||a.startsWith(`blob:`)||v(a),t&&await this.play()}async play(){this.analysable&&this.ensureGraph(this.active);try{await this.active.play()}catch(e){this.handlers.onError(e instanceof Error?e.message:`playback was refused`)}}pause(){this.active.pause()}stop(){this.active.pause(),this.active.currentTime=0,this.source=``,this.attached?.destroy(),this.attached=null}seek(e){Number.isFinite(e)&&(this.active.currentTime=Math.max(0,e))}set volume(e){this.elements.audio.volume=e,this.elements.video.volume=e}get volume(){return this.active.volume}};function y(e){switch(e.error?.code){case MediaError.MEDIA_ERR_ABORTED:return`playback was aborted`;case MediaError.MEDIA_ERR_NETWORK:return`the network dropped mid-track`;case MediaError.MEDIA_ERR_DECODE:return`this browser could not decode that`;case MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED:return`this browser cannot play that format`;default:return`playback failed`}}function ue(){return{revision:0,tracks:[],trackCount:0,index:0,playing:!1,position:0,bars:[],levels:[0,0],silent:!0,note:``,root:``}}function de(e,t){return{...t,tracks:t.tracks??e.tracks}}function b(e){let t=e.trim();if(t===``)return``;/^https?:\/\//i.test(t)||(t=`http://${t}`);let n;try{n=new URL(t)}catch{return``}let r=n.pathname.replace(/\/+$/,``);return r=r.replace(/\/api(\/.*)?$/,``),`${n.origin}${r}`}function x(e,t,n=``){let r=`${e===``?``:b(e)}${t.startsWith(`/`)?t:`/${t}`}`;return n?`${r}${r.includes(`?`)?`&`:`?`}k=${encodeURIComponent(n)}`:r}function fe(e){let t;try{t=new URL(e)}catch{return null}if(t.protocol!==`http:`&&t.protocol!==`https:`)return null;let n=/^\/api\/channels\/([^/]+)\/?$/.exec(t.pathname),r=/^\/api\/media\/(\d+)\/?$/.exec(t.pathname),i=/^\/api\/live\/?$/.test(t.pathname);if(!n&&!r&&!i)return null;let a=t.searchParams.get(`k`)??``,o=a?`${t.origin}/view/${a}`:t.origin,s=`live`;if(r&&(s=`track:${r[1]}`),n)try{s=`channel:${decodeURIComponent(n[1]??``)}`}catch{s=`channel:${n[1]??``}`}return{view:o,what:s}}function pe(e){let t=e.trim();if(t===``)return{base:``,key:``};let n;try{n=new URL(/^https?:\/\//i.test(t)?t:`http://${t}`)}catch{return{base:``,key:``}}let r=/^\/(?:admin|view|a|v)\/([^/]+)\/?$/.exec(n.pathname),i=r?.[1]??n.searchParams.get(`k`)??``;return r&&(n.pathname=`/`),n.searchParams.delete(`k`),{base:b(`${n.origin}${n.pathname}`),key:decodeURIComponent(i)}}var S=null;function C(e){if(!e&&S!==null)return S;let t=e??(typeof document>`u`?null:document.createElement(`video`));if(!t)return!1;try{let n=t.canPlayType(`video/mp4; codecs="hvc1.1.6.L93.B0"`)!==``;return e||(S=n),n}catch{return!1}}function me(e,t,n=0,r=``,i=!1){let a=[...n>0?[`kbps=${Math.round(n)}`]:[],...i?[`hevc=1`]:[]].join(`&`);return x(e,a?`/api/media/${t}?${a}`:`/api/media/${t}`,r)}function w(e){if(typeof e!=`object`||!e)return null;let t=e,n=ue(),r=(e,t)=>typeof e==`number`&&Number.isFinite(e)?e:t,i=Array.isArray(t.levels)?t.levels:[],a=Array.isArray(t.tracks)?t.tracks.map(e=>{let t=typeof e==`object`&&e?e:{};return{title:typeof t.title==`string`?t.title:`Untitled`,artist:typeof t.artist==`string`?t.artist:``,album:typeof t.album==`string`?t.album:``,duration:r(t.duration,0),...t.video===!0?{video:!0}:{},...typeof t.group==`string`&&t.group!==``?{group:t.group}:{},...typeof t.folder==`string`&&t.folder!==``?{folder:t.folder}:{},...t.remote===!0?{remote:!0}:{}}}):void 0;return{revision:r(t.revision,0),...a?{tracks:a}:{},trackCount:r(t.trackCount,a?.length??0),index:r(t.index,0),playing:t.playing===!0,position:r(t.position,0),bars:Array.isArray(t.bars)?t.bars.map(e=>r(e,0)):[],levels:[r(i[0],0),r(i[1],0)],silent:t.silent===!0,note:typeof t.note==`string`?t.note:``,root:typeof t.root==`string`?t.root:n.root}}var he=class{handlers;source=null;base=``;key=``;shape=`/admin/`;lastRevision=-1;session=``;constructor(e){this.handlers=e}get address(){return this.base}url(e){let t=x(this.base,e,this.key);return this.base===``||this.session===``?t:`${t}${t.includes(`?`)?`&`:`?`}session=${encodeURIComponent(this.session)}`}get shareLink(){return this.base===``?``:this.key===``?this.base:`${this.base}${this.shape}${this.key}`}get connected(){return this.source!==null}connect(e){let{base:t,key:n}=pe(e);this.close(),this.base=t,this.key=n,this.shape=/\/(?:view|v)\/[^/]+\/?$/.test(e.trim())?`/view/`:`/admin/`,this.lastRevision=-1,this.handlers.onStatus(`connecting`);let r=new EventSource(x(t,`/api/events`,n));this.source=r,r.onopen=()=>this.handlers.onStatus(`live`),r.onmessage=e=>{let t=w(T(e.data));t&&(t.revision<this.lastRevision||(this.lastRevision=t.revision,this.handlers.onStatus(`live`),this.handlers.onSnapshot(t)))},r.onerror=()=>{this.handlers.onStatus(`error`,`reconnecting…`)}}async send(e){if(this.base===``&&!this.connected)return;let t=await fetch(x(this.base,`/api/command`,this.key),{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify(e)});if(!t.ok){this.handlers.onStatus(`error`,`command refused (${t.status})`);return}let n=w(await t.json());n&&this.handlers.onSnapshot(n)}media(e,t=0,n=C()){return me(this.base,e,t,this.key,n)}close(){this.source?.close(),this.source=null}};function T(e){try{return JSON.parse(e)}catch{return null}}async function ge(e,t,n=``){try{let r=await fetch(x(e,`/api/state`,n),{signal:t});return r.ok?w(await r.json()):null}catch{return null}}function _e(e){if(!/^https:\/\//i.test(e))return``;let t;try{t=new URL(e).hostname.replace(/^\[|\]$/g,``)}catch{return``}return/^\d{1,3}(\.\d{1,3}){3}$/.test(t)||t.includes(`:`)?`That is an https address for a bare IP, and a certificate is issued for a name — a browser refuses it before it asks anything. Use the server's name instead (the address it printed first), or connect over http.`:``}async function ve(e,t=``,n){let r;try{r=await fetch(x(e,`/api/state`,t),{signal:n})}catch{return``}return r.ok?``:r.status===401?t===``?`That server needs its share link. Paste the whole link — the one with /admin/ or /view/ in it — or sign in as its owner.`:`That share link is not accepted by that server. It may have been restarted, which gives it a new one.`:r.status===403?`That link can listen but not drive this server.`:r.status===429?`That server is asking us to slow down. Try again in a moment.`:``}async function ye(e,t,n=``){try{let r=await fetch(x(e,`/api/health`,n),{signal:t});if(!r.ok)return null;let i=await r.json();return i.name===`nixamp`?i.version??`unknown`:null}catch{return null}}function be(e,t=globalThis.location?.protocol){return t!==`https:`||!/^http:\/\//i.test(e.trim())?``:`This page is https, and a browser refuses every request from an https page to an http one. Open that address directly, or give the server a certificate: nixamp serve --tls-cert cert.pem --tls-key key.pem.`}var xe=.14,Se=.02;function Ce(e,t){let n=[];for(let r=0;r<=e;r++){let i=r/e,a=Math.round(1*(t/1)**i),o=n[n.length-1];n.push(o===void 0?a:Math.max(a,o+1))}return n}function we(e,t){let n=[];for(let r=0;r+1<t.length;r++){let i=Math.min(t[r],e.length),a=Math.min(Math.max(t[r+1],i+1),e.length),o=0,s=0;for(let t=i;t<a;t++)o+=e[t],s++;n.push(s===0?0:o/s/255)}return n}function Te(e,t,n=xe){return t.map((t,r)=>{let i=e[r]??0;return t>=i?t:Math.max(t,i-n)})}function Ee(e,t,n=Se){return t.map((t,r)=>Math.max(t,(e[r]??0)-n))}function De(e,t,n,r,i){let{width:a,height:o}=t;if(a<=0||o<=0||n.length===0)return;e.clearRect(0,0,a,o);let s=a/n.length,c=Math.max(1,s*.72),l=Math.max(2,o*.012);e.fillStyle=i.bar,n.forEach((t,n)=>{let r=Math.max(1,t*(o-l*2));e.fillRect(n*s+(s-c)/2,o-r,c,r)}),e.fillStyle=i.peak,r.forEach((t,n)=>{let r=o-Math.max(1,t*(o-l*2))-l*2;e.fillRect(n*s+(s-c)/2,Math.max(0,r),c,l)})}function Oe(e,t){return{name:String(e?.name??e?.displayName??e?.abbreviation??``),score:typeof e?.score==`number`?e.score:typeof t==`number`?t:null,logo:typeof e?.logoUrl==`string`&&/^https?:\/\//.test(e.logoUrl)?e.logoUrl:``}}function ke(e){let t=String(e.data.state??e.tags?.find(e=>e.startsWith(`state:`))?.slice(6)??``);return t===`in`||t===`post`?t:`pre`}function Ae(e,t={}){if(!e)return``;let n=new Date(e);if(Number.isNaN(n.getTime()))return``;let r=t.now??new Date,i=t.timeZone?{timeZone:t.timeZone}:{},a=n.toLocaleDateString(t.locale,i)===r.toLocaleDateString(t.locale,i);return n.toLocaleString(t.locale,{...i,...a?{}:{weekday:`short`},hour:`numeric`,minute:`2-digit`})}function je(e,t={}){let n=e.data,r=Oe(n.away,n.awayScore),i=Oe(n.home,n.homeScore),a=ke(e),o=typeof n.statusDetail==`string`?n.statusDetail.trim():``,s;if(a===`in`)s=o?`LIVE · ${o}`:`LIVE`;else if(a===`post`)s=`FINAL`;else{let n=Ae(e.published_at,t);s=n?`Kicks off ${n}`:o||`Upcoming`}let c=[],l=n.league,u=String(l?.abbreviation??l?.name??``);u&&c.push(u),typeof n.broadcast==`string`&&n.broadcast.trim()&&c.push(n.broadcast.trim());let d=a!==`pre`,f=e=>d&&e.score!==null?` ${e.score}`:``;return{away:r,home:i,state:a,status:s,chips:c,text:`${r.name}${f(r)} – ${i.name}${f(i)}`}}var Me=`nixamp.tv`,E=/\bAFT\w*\b.*\bSilk\b|\bSilk\b.*\bAFT\w*\b|Android ?TV|Google ?TV|SMART-?TV|Tizen|Web0S|WebOS|BRAVIA|CrKey|Roku|Xbox|PlayStation|HbbTV|NetCast|VIDAA|Viera|AppleTV/i;function Ne(e,t=``,n=1,r=null){let i=new URLSearchParams(t).get(`tv`);return i===null?r!==null&&r!==``?D(r):/\bSilk\b/.test(e)&&n===0?!0:E.test(e):D(i)}function D(e){return e!==`0`&&e!==`no`&&e!==`off`&&e!==`false`}function Pe(e){return e?12:100}function Fe(e,t,n){let r=Math.max(1,Math.ceil(e/n)),i=Math.min(Math.max(0,t),r-1),a=i*n;return{page:i,from:a,to:Math.min(e,a+n),pages:r}}var O=/\.(mp3|m4a|aac|ogg|oga|opus|flac|wav)(\?.*)?$/i,k=/\.(mp4|m4v|webm|mov|mkv)(\?.*)?$/i,Ie=/\.m3u8(\?.*)?$/i,Le=/\.(m3u|pls)(\?.*)?$/i;function Re(e){try{let t=new URL(e);return k.test(t.pathname+t.search)}catch{return!1}}function A(e){try{let t=new URL(e),n=t.pathname.split(`/`).filter(Boolean).map(e=>{try{return decodeURIComponent(e)}catch{return e}}),r=(n.pop()??``).replace(/\.(m3u|pls)$/i,``);return(/^(playlist|index|list|all|episodes|feed)?$/i.test(r)?n.pop()??``:r).replace(/[-_]+/g,` `).trim()||t.hostname}catch{return`Playlist`}}function ze(e,t){let n=(/^#PLAYLIST:\s*(.+)$/im.exec(e)?.[1]?.replace(/[\u0000-\u001F]/g,` `).trim()??``)||A(t),r=[],i=(e,n)=>{if(r.length>=1e3)return;let i;try{i=new URL(e.trim(),t)}catch{return}if(i.protocol!==`https:`&&i.protocol!==`http:`)return;let a=i.pathname+i.search;if(!O.test(a)&&!k.test(a))return;let o=decodeURIComponent(i.pathname.split(`/`).filter(Boolean).pop()??i.hostname);r.push({url:i.toString(),title:n||o})},a=!1;try{a=/\.pls$/i.test(new URL(t).pathname)}catch{a=!1}if(a){let t=new Map,a=new Map;for(let n of e.split(/\r?\n/)){let e=/^(File|Title)(\d+)\s*=\s*(.*)$/i.exec(n.trim());e&&(/^file$/i.test(e[1]??``)?t:a).set(e[2]??``,e[3]??``)}for(let[e,n]of[...t.entries()].sort((e,t)=>Number(e[0])-Number(t[0])))i(n,(a.get(e)??``).trim());return{title:n,entries:r}}let o=``;for(let t of e.split(/\r?\n/)){let e=t.trim();if(e!==``){if(e.startsWith(`#`)){let t=/^#EXTINF:\s*-?[\d.]+\s*(?:,(.*))?$/i.exec(e);t&&(o=(t[1]??``).trim());continue}i(e,o),o=``}}return{title:n,entries:r}}function Be(e){let t=e.hostname.replace(/^www\.|^m\.|^music\./,``),n=e=>e&&/^[A-Za-z0-9_-]{11}$/.test(e)?e:``;if(t===`youtu.be`)return n(e.pathname.slice(1).split(`/`)[0]??null);if(t!==`youtube.com`&&t!==`youtube-nocookie.com`)return``;if(e.pathname===`/watch`)return n(e.searchParams.get(`v`));let r=/^\/(?:shorts|live|embed|v)\/([^/?]+)/.exec(e.pathname);return r?n(r[1]??null):``}function Ve(e,t=!1){let n;try{n=new URL(e.trim())}catch{return null}if(n.protocol!==`https:`&&n.protocol!==`http:`)return null;let r=Be(n);if(r){let e=n.searchParams.get(`t`);return{kind:`embed`,site:`youtube`,src:`https://www.youtube-nocookie.com/embed/${r}?autoplay=1&playsinline=1&rel=0${e&&/^\d+$/.test(e)?`&start=${e}`:``}`,label:`YouTube · ${r}`}}let i=n.hostname.replace(/^www\.|^player\./,``),a=i===`vimeo.com`?/^\/(?:video\/)?(\d+)/.exec(n.pathname)?.[1]:void 0;if(a)return{kind:`embed`,site:`vimeo`,src:`https://player.vimeo.com/video/${a}?autoplay=1&playsinline=1`,label:`Vimeo · ${a}`};if(i===`soundcloud.com`&&n.pathname.split(`/`).filter(Boolean).length>=2)return{kind:`embed`,site:`soundcloud`,src:`https://w.soundcloud.com/player/?url=${encodeURIComponent(n.toString())}&auto_play=true&visual=false`,label:`SoundCloud · ${n.pathname.split(`/`).filter(Boolean).slice(0,2).join(` / `)}`};let o=n.pathname+n.search,s=decodeURIComponent(n.pathname.split(`/`).filter(Boolean).pop()??n.hostname);return Le.test(o)?{kind:`list`,url:n.toString(),label:A(n.toString())}:O.test(o)?{kind:`direct`,url:n.toString(),video:!1,label:s}:k.test(o)||Ie.test(o)&&t?{kind:`direct`,url:n.toString(),video:!0,label:s}:null}var He=`nixamp.panels`;function Ue(){return{order:[],placement:{},collapsed:[],closed:[]}}var We=/^[a-z][a-z0-9-]{0,63}$/,Ge=/^[a-z][a-z0-9-]{0,63}:[ab]?$/;function Ke(e){if(!Array.isArray(e))return[];let t=new Set,n=[];for(let r of e)typeof r==`string`&&We.test(r)&&!t.has(r)&&(t.add(r),n.push(r));return n}function qe(e){if(!e)return Ue();let t;try{t=JSON.parse(e)}catch{return Ue()}if(!t||typeof t!=`object`||Array.isArray(t))return Ue();let n=t,r={};if(n.placement&&typeof n.placement==`object`)for(let[e,t]of Object.entries(n.placement))We.test(e)&&typeof t==`string`&&Ge.test(t)&&(r[e]=t);return{order:Ke(n.order),placement:r,collapsed:Ke(n.collapsed),closed:Ke(n.closed)}}function Je(e){return JSON.stringify(e)}function Ye(e,t){let n=new Set(e),r=t.filter(e=>n.has(e)),i=new Set(r);return e.forEach((t,n)=>{if(i.has(t))return;let a=0;for(let t=n-1;t>=0;t--){let n=r.indexOf(e[t]);if(n>=0){a=n+1;break}}r.splice(a,0,t),i.add(t)}),r}function Xe(e,t,n){let r=e.filter(e=>e!==t);return n?[...r,t]:r}var j=/^[A-Za-z0-9 .&'-]{1,20}(?::|\s-)\s+/,Ze=/(?:\s+|\s*[-|(]\s*)\d{1,2}(?::\d{2})?\s*(?:[ap]\.?m\.?)?(?:\s+[A-Z]{2,4})?\)?\s*$/i,Qe=/\s+(?:vs\.?|v\.?|at|@)\s+/i,$e=/^(?:the|a|an|live|tonight|recorded|filmed|concert|home|dinner|breakfast|lunch|midnight|night|one night|death|murder|meet me|panic|sunset|sunrise)\b/i;function et(e){let t=e.replace(Ze,``).replace(Ze,``).trim().split(Qe);return t.length===2&&t.every(e=>{let t=e.trim();return t.length>=2&&t.length<=48&&/[A-Za-z]/.test(t)&&!$e.test(t)})}function tt(e){let t=String(e??``).trim();return t===``?!1:et(t.replace(j,``))||et(t)}var nt=`nixamp.remote`,rt=`nixamp.volume`,it=`nixamp.listenHere`,at=1e4,ot=!1;function M(e){let t=document.getElementById(e);if(!t)throw Error(`nixamp: #${e} is missing from the shell`);return t}function st(){let n=null;try{n=localStorage.getItem(Me)}catch{}let r=Ne(navigator.userAgent,location.search,navigator.maxTouchPoints,n);if(document.body.classList.toggle(`tv`,r),new URLSearchParams(location.search).has(`tv`))try{localStorage.setItem(Me,r?`1`:`0`)}catch{}let i=document.getElementById(`tv-toggle`);i&&(i.textContent=r?`TV mode: on`:`TV mode`,i.title=r?`Back to the ordinary layout: lists with their own scrollbars, smaller type.`:`For a television: bigger type, lists a page at a time, and nothing to scroll but the page.`,i.addEventListener(`click`,()=>{try{localStorage.setItem(Me,r?`0`:`1`)}catch{}let e=new URL(location.href);e.searchParams.delete(`tv`),location.replace(e.toString())}));let a=Pe(r),o={status:M(`status`),source:M(`source`),install:M(`install`),video:M(`video`),audio:M(`audio`),title:M(`title-line`),album:M(`album-line`),meta:M(`meta-line`),metaBlurb:M(`meta-blurb`),liveLine:M(`live-line`),downloadNow:M(`download-now`),wayInHere:M(`way-in-here`),embed:M(`embed`),embedFrame:M(`embed-frame`),linkForm:M(`link-form`),linkUrl:M(`link-url`),linkServer:M(`link-server`),linkGoLive:M(`link-go-live`),goLiveNow:M(`go-live-now`),elapsed:M(`elapsed`),total:M(`total`),seek:M(`seek`),fullscreen:M(`fullscreen`),copyNow:M(`copy-now`),canvas:M(`spectrum`),glyphs:M(`glyphs`),levels:M(`levels`),playlist:M(`playlist`),playlistPager:M(`playlist-pager`),crumbs:M(`crumbs`),filter:M(`filter`),playlistTitle:M(`playlist-panel`),note:M(`note`),linkNote:M(`link-note`),logList:M(`log-list`),files:M(`files`),folder:M(`folder`),remoteUrl:M(`remote-url`),remoteForm:M(`remote-form`),remoteState:M(`remote-state`),disconnect:M(`disconnect`),browse:M(`browse`),panelsToggle:M(`panels-toggle`),panelsPanel:M(`panels-panel`),panelsList:M(`panels-list`),panelsReset:M(`panels-reset`),accountForm:M(`account-form`),accountEmail:M(`account-email`),accountPassword:M(`account-password`),accountSubmit:M(`account-submit`),accountToggle:M(`account-toggle`),accountProviders:M(`account-providers`),accountPanel:M(`account-panel`),accountElsewhere:M(`account-elsewhere`),welcome:M(`welcome`),welcomeCreate:M(`welcome-create`),welcomeBrowse:M(`welcome-browse`),welcomeHide:M(`welcome-hide`),accountSignOut:M(`account-signout`),accountNote:M(`account-note`),adminPanel:M(`admin-panel`),adminNote:M(`admin-note`),adminSaid:M(`admin-said`),adminConnections:M(`admin-connections`),publishPanel:M(`publish-panel`),publishNote:M(`publish-note`),publishList:M(`publish-list`),adminRestream:M(`admin-restream`),adminReplace:M(`admin-replace`),adminSource:M(`admin-source`),adminName:M(`admin-name`),adminAdd:M(`admin-add`),homeNote:M(`home-note`),loadHome:M(`load-home`),directory:M(`directory`),recentNote:M(`recent-note`),recentList:M(`recent-list`),followingNote:M(`following-note`),followingList:M(`following-list`),serversPanel:M(`servers-panel`),serversNote:M(`servers-note`),serversList:M(`servers-list`),favoritesPanel:M(`favorites-panel`),favoritesNote:M(`favorites-note`),favoritesList:M(`favorites-list`),favHere:M(`fav-here`),partiesPanel:M(`parties-panel`),partiesNote:M(`parties-note`),partiesList:M(`parties-list`),partyForm:M(`party-form`),partyCode:M(`party-code`),connectionsNote:M(`connections-note`),connectionsList:M(`connections-list`),catalogsPanel:M(`catalogs-panel`),catalogsNote:M(`catalogs-note`),catalogsForm:M(`catalogs-form`),catalogSource:M(`catalog-source`),catalogName:M(`catalog-name`),catalogsList:M(`catalogs-list`),catalogsCrumbs:M(`catalogs-crumbs`),catalogsFilter:M(`catalogs-filter`),catalogsEntries:M(`catalogs-entries`),notifyPanel:M(`notify-panel`),notifyNote:M(`notify-note`),notifyWeb:M(`notify-web`),notifyEmail:M(`notify-email`),notifySms:M(`notify-sms`),notifyPhone:M(`notify-phone`),notifyPhoneForm:M(`notify-phone-form`),notifyPhoneNote:M(`notify-phone-note`),directoryNote:M(`directory-note`),directoryList:M(`directory-list`),onairPanel:M(`onair-panel`),onairNote:M(`onair-note`),onairList:M(`onair-list`),sharePanel:M(`share-panel`),trollboxPanel:M(`trollbox-panel`),trollboxNote:M(`trollbox-note`),trollboxList:M(`trollbox-list`),trollboxForm:M(`trollbox-form`),trollboxInput:M(`trollbox-input`),shareNote:M(`share-note`),shareLink:M(`share-link`),shareCopy:M(`share-copy`),sharePhone:M(`share-phone`),shareSend:M(`share-send`),liveControls:M(`live-controls`),goLive:M(`go-live`),stopLive:M(`stop-live`),shareTo:M(`share-to`),listenOnly:M(`listen-only`),listenHere:M(`listen-here`),volume:M(`volume`),prev:M(`prev`),playPause:M(`play-pause`),stop:M(`stop`),next:M(`next`),shareNow:M(`share-now`)},s={rename:`<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z"/></svg>`,eye:`<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12Z"/><circle cx="12" cy="12" r="3"/></svg>`,gear:`<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1Z"/></svg>`,live:`<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="2.5"/><path d="M8.5 15.5a5 5 0 0 1 0-7"/><path d="M15.5 8.5a5 5 0 0 1 0 7"/><path d="M5.6 18.4a9 9 0 0 1 0-12.8"/><path d="M18.4 5.6a9 9 0 0 1 0 12.8"/></svg>`,link:`<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10 13a5 5 0 0 0 7.5.5l3-3a5 5 0 0 0-7-7l-1.7 1.7"/><path d="M14 11a5 5 0 0 0-7.5-.5l-3 3a5 5 0 0 0 7 7l1.7-1.7"/></svg>`,copy:`<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="12" height="12" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg>`,restart:`<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12a9 9 0 1 1-3-6.7"/><path d="M21 3v6h-6"/></svg>`,remove:`<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>`,check:`<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m5 12 5 5L20 7"/></svg>`},c=(e,t)=>{e.innerHTML=s[t]},l=document.querySelector(`meta[name="nixamp-shell-title"]`)?.content||document.title||`nixamp`,u=`local`,d=``,f=[],p=``,m=!0,h=``;function ee(e){try{return new URL(e).origin}catch{return e}}let te=!1,g=``,_=0,ne=new Map,ie=e=>ne.get(K(e))??null;function ae(e,t){let n=n=>{te=n,g=``,o.remoteUrl.value=n?e.view:e.admin??e.view,t?.(),o.remoteForm.requestSubmit()},r=document.createElement(`button`);r.type=`button`,r.className=`icon way-in`,c(r,`eye`),r.title=`Viewer: browse and watch. Changes nothing on the server.`,r.setAttribute(`aria-label`,`View ${e.name}`),r.addEventListener(`click`,()=>n(!0));let i=document.createElement(`button`);return i.type=`button`,i.className=`icon way-in`,c(i,`gear`),i.disabled=e.admin===null,i.title=e.admin?`Admin: drive this server. What plays, what is live, what is on it.`:X?`Admin: you do not administer this server.`:`Admin: sign in as this server's owner to administer it.`,i.setAttribute(`aria-label`,`Administer ${e.name}`),i.addEventListener(`click`,()=>n(!1)),[r,i]}let se=``,v=[],ce=0,y=ue(),b=`idle`,S=``,C=`Pick files, or connect to a nixamp running somewhere else.`,me=!1,w=-1,T=null,xe=0,Se=0,Oe=!1,Ae=()=>Se>0||Oe;async function E(e){Se+=1,V();try{return await e()}finally{--Se,V()}}let D=null,O=null,k=null,Ie=null;function Le(){o.embed.hidden||(o.embedFrame.src=`about:blank`),o.embed.hidden=!0}let A=null,Be=``,We=null;function Ge(e,t,n=null,r=!1){let i=`${t}|${e}`;if(Be=i,We&&clearTimeout(We),We=null,!r){if(A?.key===i)return;A=null}if(u!==`remote`||e.trim()===``)return;let a=new URLSearchParams({name:e,kind:t});n&&a.set(`year`,String(n)),fetch(L.url(`/api/enrich?${a}`)).then(e=>e.ok?e.json():{match:null}).then(r=>{if(Be!==i)return;A={key:i,match:r.match??null},V();let a=A.match;a?.kind===`fixture`&&ke(a)!==`post`&&(We=setTimeout(()=>{We=null,!(Be!==i||I.source===``&&!T)&&Ge(e,t,n,!0)},6e4))}).catch(()=>void 0)}let Ke=!1,j=``,Ze=``,Qe=null,$e=``,et=``,st=``,N=Array(24).fill(0),P=Array(24).fill(0),ct=[],F=()=>u===`remote`&&!o.listenHere.checked,lt=()=>u===`remote`&&!o.adminPanel.hidden,ut=!1,dt=()=>lt()||u===`remote`&&ut;function ft(){try{if(localStorage.getItem(`nixamp.hls`)===`1`)return!0}catch{}return typeof MediaSource<`u`?!1:o.video.canPlayType(`application/vnd.apple.mpegurl`)!==``}function pt(){if(u!==`remote`||k)return null;if(D?.catalog&&D.entry)return{kind:`entry`,catalog:D.catalog,entry:D.entry};if(T)return{kind:`channel`,id:T.id,name:T.name};let e=y.tracks[R()];return e&&(I.source!==``||F())?{kind:`track`,index:R(),name:t(e)}:null}async function mt(e,t){t.disabled=!0;let n=e.kind===`entry`?e.entry.title:e.name;C=`Putting ${n} on the air…`,V();try{let r=``,i=``,a=!0;{let t=e.kind===`track`?`/api/tracks/${e.index}/live`:e.kind===`entry`?`/api/catalogs/${encodeURIComponent(e.catalog.id)}/entries/${encodeURIComponent(e.entry.id)}/live`:`/api/channels/${encodeURIComponent(e.id)}/keep`,o=await fetch(L.url(t),{method:`POST`}),s=await o.json().catch(()=>({}));if(!o.ok){C=s.error??`${n} would not go on the air.`,V();return}i=e.kind===`channel`?e.id:s.channel??``,a=s.video!==!1,r=`channel:${i}`}if(lt()&&(Ke?await fetch(L.url(`/api/live/start`),{method:`POST`}).catch(()=>void 0):await ui(!0)),await Gr(),Q(),i!==``&&T?.id!==i){let t=e.kind===`entry`?{kind:`channel`,catalog:e.catalog,entry:{id:e.entry.id,title:e.entry.title,group:e.entry.group??``,live:e.entry.live??!0,...e.entry.logo?{logo:e.entry.logo}:{}}}:void 0;await Qr({id:i,name:n,video:a},!0,t)}let o=ii(r),s=j?` Call ${Ze||`the line`} and key ${j} to talk about it.`:``;o===``?C=`${n} is on the air and you are watching it.${s}`:(await ri(o,t,`✓`),C=`${n} is on the air and you are watching it. Link copied.${s}`),V()}catch{C=`could not reach the server`,V()}finally{t.disabled=!1}}function ht(e,t=`Join live`){c(e,`live`),e.append(document.createTextNode(` ${t}`))}function gt(e,t){let n=document.createElement(`button`);return n.type=`button`,n.className=`row-copy row-live`,c(n,`live`),n.title=`Go live with ${t}: on the air for everyone, listed, link copied`,n.setAttribute(`aria-label`,`Go live with ${t}`),n.addEventListener(`click`,t=>{t.stopPropagation(),mt(e(),n)}),n}let I=new le({audio:o.audio,video:o.video},{onTime:(e,t)=>{let n=v[ce];u===`local`&&n&&t>0&&n.duration!==t&&(n.duration=t),V()},onEnded:()=>{$r()||Jt()||u===`remote`&&w<0&&!F()||Tt(1)},onState:()=>V(),onBusy:e=>{Oe!==e&&(Oe=e,V())},onError:e=>{$r()||(C=e,V(),Yr())}}),L=new he({onSnapshot:e=>{if(y=de(y,e),g.startsWith(`track:`)&&y.tracks.length>0){let e=Number(g.slice(6));if(g=``,Number.isInteger(e)&&e>=0&&e<y.tracks.length){let t=_;Ct(e).then(()=>{t>0&&(I.seek(t),setTimeout(()=>I.seek(t),600))})}}F()&&(N=e.bars.length>0?e.bars:N,P=Ee(P,N)),V()},onStatus:(e,t)=>{b=e,S=t??``,V()}}),_t=()=>u===`remote`?y.tracks.length:v.length,R=()=>u===`remote`?F()||w<0?y.index:Math.min(w,Math.max(0,y.tracks.length-1)):ce,z=()=>{if(T)return T.name;if(k)return k.label;let e=u===`remote`?y.tracks[R()]:v[R()];return e?t(e):`Nothing loaded.`},vt=()=>T?`live on this server`:k?`playing here, in this browser`:D?.kind===`live`&&u===`remote`?`live on ${d||`this server`}`:(u===`remote`?y.tracks[R()]:v[R()])?.album||`—`;function yt(){if(u!==`remote`||T===null&&D?.kind!==`live`){o.liveLine.hidden=!0,o.liveLine.replaceChildren();return}let e=d||`this server`,t=T?T.name:O?.server.nowPlaying||z(),n=[T?`Live on ${e}: `:`Live from ${e}, now playing: `,di(t)],r=T?O?.channels.find(e=>e.id===T?.id)?.code??``:Ke?j:``;r&&n.push(`. To talk about it, call `,di(Ze||`the line`),` and key `,di(r),`.`);let i=n.map(e=>typeof e==`string`?e:e.textContent).join(``);o.liveLine.dataset.drawn!==i&&(o.liveLine.dataset.drawn=i,o.liveLine.hidden=!1,o.liveLine.replaceChildren(...n.map(e=>typeof e==`string`?document.createTextNode(e):e)))}let bt=()=>F()?y.tracks[R()]?.duration??0:I.duration,xt=()=>F()?y.position:I.position,St=()=>F()?y.playing:I.playing;async function B(e){if(u===`remote`){if(F()){await L.send({type:`play`,index:e});return}await Ct(e);return}let t=v[e];t&&(ce=e,T=null,D={kind:`file`},await E(()=>I.load(t,!0)),U(t.video),Wt(),V())}async function Ct(e){let t=y.tracks[e];t&&(w=e,T=null,D={kind:`file`},Ge(t.title,`auto`),await E(()=>I.load({title:t.title,artist:t.artist,album:t.album,duration:t.duration,url:L.media(e,0),video:t.video===!0,objectUrl:!1},!0)),U(t.video===!0),Wt())}async function wt(){if(F()){await L.send({type:`toggle`});return}_t()!==0&&(I.playing?I.pause():I.position>0?await I.play():await B(R()),V())}async function Tt(e){let t=_t();if(t!==0){if(F()){await L.send({type:e>0?`next`:`prev`});return}await B((R()+e+t)%t)}}async function Et(){if(Le(),k=null,Ie=null,F()){await L.send({type:`stop`});return}T=null,D=null,I.stop(),N=Array(24).fill(0),P=[...N],V()}let Dt=(e,t)=>`L${`▮`.repeat(Math.round(e*6)).padEnd(6,`·`)} R${`▮`.repeat(Math.round(t*6)).padEnd(6,`·`)}`,Ot=e=>`▁▂▃▄▅▆▇█`[Math.max(0,Math.min(7,Math.round(e*7)))],kt=``;function At(){let t=[],n=``,r=null,i=T?O?.channels.find(e=>e.id===T?.id):void 0,a=I.source===``&&!T&&!(F()&&y.tracks[R()]);if(!a){T?t.push(D?.entry?.live===!1?`ON DEMAND · LIVE CHANNEL`:`LIVE`):D?.kind===`vod`?t.push(`ON DEMAND`):D?.kind===`live`?t.push(`LIVE`):u===`remote`&&F()?t.push(`ON THE SERVER`):t.push(`FILE`),!o.video.hidden&&o.video.videoWidth>0?t.push(`${o.video.videoWidth}×${o.video.videoHeight}`):o.video.hidden?t.push(`audio`):t.push(`video`),i?(t.push(i.via===`pull`?`${i.listeners} watching`:`${i.listeners} listening · over ${i.via}`),i.startedAt>0&&t.push(`on air ${e(Math.max(0,(Date.now()-i.startedAt)/1e3))}`),i.redials&&t.push(`redialled ${i.redials}×`),lt()&&i.error&&t.push(i.error)):u===`remote`&&!T?(y.tracks[R()]&&_t()>0&&t.push(`track ${R()+1} of ${_t()}`),F()&&O&&t.push(`${O.server.playing?`playing`:`stopped`} on ${d||`the server`}`)):u===`local`&&_t()>0&&t.push(`track ${R()+1} of ${_t()}`),D?.catalog!==void 0&&(D.kind===`channel`?T!==null:D.kind===`vod`&&I.source!==``)&&D?.catalog&&(t.push(D.entry?.group?`${D.catalog.name} › ${D.entry.group}`:D.catalog.name),n=D.entry?.logo??``);let a=A?.key===Be?A.match:null;if(a?.kind===`fixture`)r=je(a),t.unshift(r.status,...r.chips);else if(a){a.image&&(n=a.image);let e=a.data;if(a.kind===`title`){a.year&&t.push(String(a.year));let n=typeof e.rating==`number`?e.rating:null;n&&t.push(`★ ${n.toFixed(1)}`);let r=Array.isArray(e.genres)?e.genres.slice(0,2).map(String):[];r.length&&t.push(r.join(` · `));let i=typeof e.runtimeMin==`number`?e.runtimeMin:0;i&&t.push(`${i} min`)}else if(a.kind===`channel`){let n=typeof e.country==`string`?e.country:``,r=Array.isArray(e.categories)?e.categories.slice(0,2).map(String):[],i=typeof e.network==`string`?e.network:``;n&&t.push(n),r.length&&t.push(r.join(` · `)),i&&t.push(i)}}if(T&&D?.link){let e=``;try{e=new URL(D.link.url).hostname.replace(/^www\./,``)}catch{}t.push(D.link.extractor&&D.link.extractor!==`direct`&&D.link.extractor!==`generic`?`${D.link.extractor} · ${e}`:e||`link`)}Ke&&j&&F()&&!T&&D?.kind!==`live`&&t.push(Ze?`☎ ${Ze} · key ${j}`:`☎ code ${j}`)}let s=A?.key===Be?A.match:null,c=!a&&!r&&s?.summary?s.summary:``,l=`${n}|${r?`${r.away.logo}|${r.home.logo}|${r.text}`:``}|${t.join(`|`)}|${c}`;if(l===kt)return;kt=l,o.meta.hidden=t.length===0,o.metaBlurb.textContent=c,o.metaBlurb.hidden=c===``;let f=[];if(r){let e=document.createElement(`div`);e.className=`meta-score`;let t=(e,t)=>{let n=[],i=document.createElement(`span`);if(i.className=`meta-team`,i.textContent=e.name,n.push(i),r?.state!==`pre`&&e.score!==null){let t=document.createElement(`b`);t.className=`meta-points`,t.textContent=String(e.score),n.push(t)}if(e.logo!==``){let r=document.createElement(`img`);r.className=`meta-team-logo`,r.alt=``,r.src=e.logo,r.addEventListener(`error`,()=>{r.hidden=!0}),t?n.unshift(r):n.push(r)}return n},n=document.createElement(`span`);n.className=`meta-dash`,n.textContent=`–`,e.replaceChildren(...t(r.away,!0),n,...t(r.home,!1)),f.push(e)}if(n!==``&&/^https?:\/\//.test(n)){let e=document.createElement(`img`);e.className=s?.kind===`title`&&n===s.image?`meta-logo meta-poster`:`meta-logo`,e.alt=``,e.src=n,e.addEventListener(`error`,()=>{e.hidden=!0}),f.push(e)}for(let e of t){let t=document.createElement(`span`);t.className=r?.state===`in`&&e===r.status?`meta-chip chip-live`:`meta-chip`,t.textContent=e,f.push(t)}o.meta.replaceChildren(...f)}function V(){let t=_t(),n=St(),r=Ae();o.status.textContent=r?`LOADING`:n?`▶ PLAYING`:`■ STOPPED`,o.status.dataset.playing=r?`loading`:String(n),o.title.textContent=z(),yt(),At(),o.goLiveNow.hidden=!dt()||pt()===null;let i=n?`${z()} · ${l}`:l;document.title!==i&&(document.title=i),o.copyNow.hidden=I.source===``,o.shareNow.hidden=Vn()===``,In(),o.downloadNow.hidden=!(T&&D?.link?.download),Xt(),o.album.textContent=vt();let a=xt(),s=bt();o.elapsed.textContent=e(a),o.total.textContent=s>0?e(s):`--:--`,me||(o.seek.value=String(s>0?Math.round(a/s*1e3):0),o.seek.disabled=s<=0||F()),o.playPause.textContent=n?`❚❚`:`▶`,o.playPause.setAttribute(`aria-label`,n?`Pause`:`Play`),o.playlistTitle.dataset.title=u===`remote`?`Files on ${d||`this server`} (${t.toLocaleString()})`:`Playlist (${t})`,o.source.textContent=u===`remote`?`connected · ${d||L.address.replace(/^https?:\/\//,``)||`—`}`:v.length>0?`local · ${v.length} files`:`no source`,Ut(),o.remoteState.textContent=u===`remote`?`${b}${S?` — ${S}`:``}`:`not connected`,o.remoteState.dataset.status=u===`remote`?b:`idle`,o.disconnect.hidden=u!==`remote`;let c=u===`remote`&&y.note!==``?y.note:C;o.note.textContent=c,o.note.hidden=c===``,o.linkNote.textContent=c,o.linkNote.hidden=c===``,En(c),Rt(),o.glyphs.textContent=N.map(Ot).join(``);let[f,p]=F()?y.levels:I.levels();o.levels.textContent=Dt(f,p)}let jt=``,Mt=-1,H=``,Nt=0;function Pt(e){H=e,Nt=0,jt=``,Rt()}function Ft(e,t,n,r,i){if(o.playlistPager.hidden=n<=1,n<=1){o.playlistPager.replaceChildren();return}let a=(e,t,r)=>{let i=document.createElement(`button`);return i.type=`button`,i.className=`ghost`,i.textContent=e,i.title=r,i.disabled=t<0||t>=n,i.addEventListener(`click`,()=>{Nt=t,jt=``,Rt(),o.playlist.scrollIntoView({block:`nearest`})}),i},s=document.createElement(`span`);s.className=`pager-where`,s.textContent=`${r+1}–${i} of ${e.toLocaleString()}`,o.playlistPager.replaceChildren(a(`‹ Previous`,t-1,`The page before this one`),s,a(`Next ›`,t+1,`The page after this one`))}function It(e){if(o.crumbs.hidden=!e,!e)return;let t=H===``?[]:H.split(`/`),n=(e,t,n)=>{if(n){let t=document.createElement(`span`);return t.className=`here`,t.textContent=e,t}let r=document.createElement(`button`);return r.type=`button`,r.textContent=e,r.addEventListener(`click`,()=>Pt(t)),r},r=[n(`All files`,``,t.length===0)],i=``;t.forEach((e,a)=>{i=i===``?e:`${i}/${e}`;let o=document.createElement(`span`);o.textContent=`/`,r.push(o,n(e,i,a===t.length-1))}),o.crumbs.replaceChildren(...r)}function Lt(e,t){let n=document.createElement(`li`);n.className=`folder`;let r=document.createElement(`span`);r.className=`name`,r.textContent=`${e}/`;let i=document.createElement(`span`);return i.className=`count`,i.textContent=`${t} file${t===1?``:`s`}`,n.append(r,i),n.tabIndex=0,n.addEventListener(`click`,()=>Pt(H===``?e:`${H}/${e}`)),n}function Rt(){let n=u===`remote`?y.tracks.map(e=>({name:t(e),seconds:e.duration,group:e.group??``,folder:e.folder??``,remote:e.remote===!0})):v.map(e=>({name:t(e),seconds:e.duration,group:``,folder:``,remote:!1})),r=o.filter.value.trim().toLowerCase(),i=n.map((e,t)=>({...e,index:t})).filter(e=>!e.remote).filter(e=>r===``||`${e.folder}/${e.name}`.toLowerCase().includes(r)),s=e=>r!==``||H===``||e===H||e.startsWith(`${H}/`),l=e=>r!==``||e===H,d=e=>{let t=H===``?e:e.slice(H.length+1),n=t.indexOf(`/`);return n===-1?t:t.slice(0,n)},f=new Map;for(let e of i){if(!s(e.folder)||l(e.folder))continue;let t=d(e.folder);t!==``&&f.set(t,(f.get(t)??0)+1)}let p=i.filter(e=>l(e.folder)&&s(e.folder)),m=[...f].sort((e,t)=>e[0].localeCompare(t[0],void 0,{numeric:!0})),h=Fe(m.length+p.length,Nt,a);Nt=h.page;let ee=m.slice(h.from,h.to),te=p.slice(Math.max(0,h.from-m.length),Math.max(0,h.to-m.length)),g=`${u}:${H}:${r}:${h.page}/${a}:${m.join(`,`)}:${p.map(e=>`${e.index}@${e.name}@${e.seconds}@${e.group}`).join(`|`)}`;if(g!==jt){jt=g,It(r===``&&(m.length>0||H!==``)),Ft(m.length+p.length,h.page,h.pages,h.from,h.to);let t=[];for(let[e,n]of ee)t.push(Lt(e,n));let n=``,i=p.some(e=>e.group!==``);for(let r of te){r.group!==n&&(i||r.group!==``)&&(n=r.group,t.push(zt(r.group)));let a=document.createElement(`li`);a.className=`row`,a.tabIndex=0,a.dataset.index=String(r.index);let o=document.createElement(`span`);o.className=`n`,o.textContent=String(r.index+1).padStart(2,` `);let s=document.createElement(`span`);s.className=`name`,s.textContent=r.name;let l=document.createElement(`span`);if(l.className=`time`,l.textContent=r.seconds>0?e(r.seconds):`--:--`,a.append(o,s,l),u===`remote`){let e=document.createElement(`button`);e.type=`button`,e.className=`row-copy`,c(e,`copy`),e.title=`Copy a link that plays this here, from where it is`,e.setAttribute(`aria-label`,`Copy a link that plays ${r.name}`),e.addEventListener(`click`,t=>{t.stopPropagation(),ri(ii(`track:${r.index}`,w===r.index?I.position:0),e,`✓`)}),a.append(e),dt()&&a.append(gt(()=>({kind:`track`,index:r.index,name:r.name}),r.name))}t.push(a)}o.playlist.replaceChildren(...t)}let _=R(),ne=St(),re;for(let e of Array.from(o.playlist.children)){let t=e,n=Number(t.dataset.index),r=Number.isInteger(n)&&n===_;t.classList.toggle(`selected`,r),t.classList.toggle(`playing`,r&&ne),r&&(re=t)}if(_!==Mt){Mt=_;let e=p.findIndex(e=>e.index===_);if(e>=0&&!re){Nt=Math.floor((m.length+e)/a),jt=``,Rt();return}re?.scrollIntoView({block:`nearest`})}}function zt(e){let t=document.createElement(`li`);t.className=`group`;let n=document.createElement(`span`);if(n.className=`group-name`,n.textContent=e===``?`This server's library`:e,t.append(n),e!==``&&!o.adminPanel.hidden){let n=document.createElement(`button`);n.type=`button`,n.className=`group-remove`,n.textContent=`×`,n.title=`Remove ${e} from the playlist`,n.setAttribute(`aria-label`,`Remove ${e} from the playlist`),n.addEventListener(`click`,t=>{t.stopPropagation(),Bt(e)}),t.append(n)}return t}async function Bt(e){try{let t=await fetch(L.url(`/api/source/remove`),{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({group:e})}),n=await t.json();W(t.ok?`Removed ${n.removed??0} tracks from ${e}.`:n.error??`that did not work`)}catch{W(`could not reach the server`)}}function Vt(){let t=o.canvas,n=Math.min(2,globalThis.devicePixelRatio||1),r=Math.round(t.clientWidth*n),i=Math.round(t.clientHeight*n);r>0&&i>0&&(t.width!==r||t.height!==i)&&(t.width=r,t.height=i);let a=t.getContext(`2d`);if(F())P=Ee(P,N);else{let e=I.read();e.length>0&&(ct.length!==25&&(ct=Ce(24,e.length)),N=Te(N,we(e,ct)),P=Ee(P,N))}if(a){let e=getComputedStyle(document.documentElement);De(a,{width:t.width,height:t.height},N,P,{bar:e.getPropertyValue(`--green`).trim()||`#4af689`,peak:e.getPropertyValue(`--green-dim`).trim()||`#227a4a`,background:`transparent`})}if(St()){o.glyphs.textContent=N.map(Ot).join(``);let[t,n]=F()?y.levels:I.levels();o.levels.textContent=Dt(t,n),o.elapsed.textContent=e(xt());let r=bt();!me&&r>0&&(o.seek.value=String(Math.round(xt()/r*1e3)))}requestAnimationFrame(Vt)}let Ht=``;function Ut(){if(u!==`remote`){o.wayInHere.hidden=!0,Ht=``;return}let e=lt()&&!te,t=se||L.address,n=e?null:ie(L.address),r=`${t}|${n??``}|${e?`admin`:`view`}`;if(r===Ht)return;Ht=r;let[i,a]=ae({name:d||`this server`,view:t,admin:n});i.hidden=!e,a.hidden=e,o.wayInHere.replaceChildren(i,a),o.wayInHere.hidden=!1}function U(e){o.video.hidden=!e,o.fullscreen.hidden=!e,Le(),k=null,Ie=null}function Wt(){`mediaSession`in navigator&&(navigator.mediaSession.metadata=new MediaMetadata({title:z(),album:vt(),artist:`nixamp`,artwork:[{src:`/icons/icon-512.png`,sizes:`512x512`,type:`image/png`}]}),navigator.mediaSession.setActionHandler(`play`,()=>void wt()),navigator.mediaSession.setActionHandler(`pause`,()=>void wt()),navigator.mediaSession.setActionHandler(`nexttrack`,()=>void Tt(1)),navigator.mediaSession.setActionHandler(`previoustrack`,()=>void Tt(-1)))}o.filter.addEventListener(`input`,()=>{Nt=0,jt=``,Rt()}),o.playlist.addEventListener(`keydown`,e=>{if(e.key!==`Enter`&&e.key!==` `)return;let t=e.target?.closest(`li.row, li.folder`);t&&t===e.target&&(e.preventDefault(),t.click())}),o.playlist.addEventListener(`click`,e=>{let t=e.target.closest(`li`),n=Number(t?.dataset.index);Number.isInteger(n)&&B(n)}),o.fullscreen.addEventListener(`click`,()=>{let e=o.video;if(document.fullscreenElement){document.exitFullscreen().catch(()=>{});return}if(typeof e.requestFullscreen==`function`){e.requestFullscreen().catch(()=>{e.webkitEnterFullscreen?.()});return}e.webkitEnterFullscreen?.()}),o.prev.addEventListener(`click`,()=>void Tt(-1)),o.next.addEventListener(`click`,()=>void Tt(1)),o.stop.addEventListener(`click`,()=>void Et()),o.playPause.addEventListener(`click`,()=>void wt());async function Gt(e){let t=Ve(e,ft());if(!t){if(!dt()){C=u===`remote`?`That link would need this server to fetch it, which is going live with it: sign in to nixamp.com, or use the server's control link. YouTube, Vimeo, SoundCloud and links straight to a file play here.`:`That link would need a server to fetch it: pick one to go live on, or connect to one. YouTube, Vimeo, SoundCloud and links straight to a file play here.`,V();return}await Yt(e);return}if(T=null,I.stop(),t.kind===`embed`){D={kind:`link`,link:{url:e,extractor:t.site,download:!1,live:!1,video:!0}},U(!1),o.embedFrame.src=t.src,o.embed.hidden=!1,k={url:e,label:t.label,kind:`embed`},C=`Playing ${t.label} here, in this browser.`,V();return}if(t.kind===`list`){await Kt(e,t.label);return}D={kind:`link`,link:{url:e,extractor:`direct`,download:!1,live:!1,video:t.video}},await E(()=>I.load({title:t.label,artist:``,album:``,duration:0,url:t.url,video:t.video,objectUrl:!1},!0)),U(t.video),k={url:e,label:t.label,kind:`direct`},C=`Playing ${t.label} here, in this browser.`,V()}async function Kt(e,t){C=`Reading ${t}…`,V();let n=``;try{let t=await fetch(e,{mode:`cors`});if(!t.ok)throw Error(`HTTP ${t.status}`);n=await t.text()}catch(e){C=`That playlist could not be read here (${e.message}). Go live with it instead: the server reads it.`,V();return}let r=ze(n,e);if(r.entries.length===0){C=`Nothing in that playlist is a file this browser plays. Go live with it instead: the server reads it.`,V();return}Ie={url:e,title:r.title||t,entries:r.entries,at:0},await qt()}async function qt(){let e=Ie,t=e?.entries[e.at];if(!e||!t)return;let n=Re(t.url);D={kind:`link`,link:{url:e.url,extractor:`playlist`,download:!1,live:!1,video:n}},await E(()=>I.load({title:t.title,artist:e.title,album:``,duration:0,url:t.url,video:n,objectUrl:!1},!0)),U(n),k={url:e.url,label:e.title,kind:`direct`},Ie=e,C=`Playing ${e.title} here: ${e.at+1} of ${e.entries.length}, ${t.title}.`,V()}function Jt(){let e=Ie;return!e||e.entries.length===0?!1:(e.at=(e.at+1)%e.entries.length,qt(),!0)}async function Yt(e){if(u!==`remote`){C=`Pick a server to go live on, or connect to one, to put a link on the air.`,V();return}C=`Asking ${d||`the server`} to fetch ${e}…`,V(),await E(async()=>{let t,n={};try{t=await fetch(L.url(`/api/links/play`),{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({url:e,live:!0})}),n=await t.json().catch(()=>({}))}catch{C=`could not reach the server`;return}if(!t.ok||!n.channel){C=n.error??`that link would not play.`;return}try{await fetch(L.url(`/api/channels/${encodeURIComponent(n.channel)}/keep`),{method:`POST`})}catch{}await Qr({id:n.channel,name:n.name||e,video:n.video!==!1},!0,{kind:`channel`,link:{url:e,extractor:n.extractor??``,download:n.download===!0,live:n.live===!0,video:n.video!==!1}}),(n.entries??0)>1&&(C=`${n.name||e} is on the air: ${n.entries} entries, played in turn.`)}),V()}o.linkForm.addEventListener(`submit`,e=>{e.preventDefault();let t=o.linkUrl.value.trim();t!==``&&Gt(t)});function Xt(){let e=u===`remote`?L.address:``,t=f.filter(t=>ee(t.url)!==ee(e)),n=JSON.stringify({here:e,name:d,carries:m,others:t});if(n===p)return;p=n;let r=[];if(e!==``&&m){let t=document.createElement(`option`);t.value=``,t.textContent=`on ${d||e}`,r.push(t)}else{let e=document.createElement(`option`);e.value=``,e.textContent=`go live on…`,r.push(e)}for(let e of t){let t=document.createElement(`option`);t.value=e.url,t.textContent=e.name,r.push(t)}o.linkServer.replaceChildren(...r),o.linkServer.value=e!==``&&!m&&t[0]?t[0].url:``,o.linkServer.hidden=t.length===0}async function Zt(){try{let e=await fetch(`/api/directory`);if(!e.ok)return;f=((await e.json()).streams??[]).map(e=>({name:e.name,url:e.url})),Xt()}catch{}}async function Qt(){let e=o.linkUrl.value.trim()||k?.url||``;if(e===``){C=`Paste a link first: a file, an .m3u playlist, an IPTV feed, a YouTube page.`,V();return}let t=o.linkServer.value;if(t!==``&&(u!==`remote`||ee(t)!==ee(L.address))){let n=f.find(e=>e.url===t)?.name??t;h=e,C=`Connecting to ${n} to go live with it…`,V(),$t(t);return}if(u!==`remote`){C=f.length>0?`Pick a server to go live on, beside the link.`:`Connect to a server first: Browse the directory, or paste its address in the Remote panel.`,V(),f.length>0&&o.linkServer.focus();return}if(!m){C=`${d||`This server`} has no ffmpeg, so it cannot carry a link.`+(f.length>0?` Pick a server to go live on, beside the link.`:``),V();return}if(!dt()){C=`Sign in to nixamp.com to go live here, or use the server's control link.`,V();return}await Yt(e)}function $t(e){let t=o.linkUrl.value.trim();te=!0,g=``,o.remoteUrl.value=e,o.remoteForm.requestSubmit(),o.linkUrl.value=t}o.linkGoLive.addEventListener(`click`,()=>{Qt()}),o.linkServer.addEventListener(`change`,()=>{let e=o.linkServer.value;e!==``&&$t(e)}),o.downloadNow.addEventListener(`click`,()=>{let e=D?.link;if(!e||u!==`remote`)return;let t=e.video?``:`&audio=1`;globalThis.open(L.url(`/api/links/download?url=${encodeURIComponent(e.url)}${t}`),`_blank`),C=`Fetching it through the server; your browser will save it when it arrives.`,V()}),o.goLiveNow.addEventListener(`click`,()=>{let e=pt();e&&mt(e,o.goLiveNow)}),o.seek.addEventListener(`input`,()=>{me=!0}),o.seek.addEventListener(`change`,()=>{let e=bt();e>0&&I.seek(Number(o.seek.value)/1e3*e),me=!1}),o.volume.addEventListener(`input`,()=>{let e=Number(o.volume.value)/100;I.volume=e;try{localStorage.setItem(rt,String(e))}catch{}});let en=e=>{e.addEventListener(`change`,()=>{let t=re(Array.from(e.files??[]));if(t.length===0){C=`Nothing playable in that selection.`,V();return}oe(v),v=t,ce=0,u=`local`,L.close(),C=``,B(0)})};en(o.files),en(o.folder),o.remoteForm.addEventListener(`submit`,e=>{e.preventDefault();let t=o.remoteUrl.value,{base:n,key:r}=pe(t);if(n===``){C=`That is not an address.`,V();return}(async()=>{b=`connecting`,V();let e=be(n);if(e){b=`error`,S=e,C=e,h=``,u=`local`,V();return}if(await ye(n,void 0,r)===null){b=`error`;let e=_e(n);S=e?`needs the server's name`:`not answering`,C=e||`Nothing answered at ${n}. If that is your machine, it is off or nixamp is not running on it; otherwise check the address.`,h=``,u=`local`,V();return}let i=await ve(n,r);if(i){b=`error`,S=i,C=i,h=``,u=`local`,V();return}u=`remote`,C=``;try{localStorage.setItem(nt,t.trim())}catch{}L.connect(t),Q(),Yn(),Jr(!0),pn(),Gr(),V()})()});let tn=``,nn=null,rn=async(e=!1)=>{o.directory.hidden=!1,e||(o.directoryNote.textContent=`Looking for live streams…`,o.directoryList.replaceChildren()),nn||=setInterval(()=>{!o.directory.hidden&&document.visibilityState===`visible`&&rn(!0)},at);let t;try{let n=await fetch(`/api/directory`);if(!n.ok)throw Error(String(n.status));let r=await n.json();t=r.streams??[],f=t.map(e=>({name:e.name,url:e.url})),Xt();let i=JSON.stringify({streams:t.map(({...e})=>{let{updatedAt:t,startedAt:n,...r}=e;return r}),recent:r.recent??[],me:X});if(e&&i===tn)return;tn=i,gn(r.recent??[])}catch{e||(o.directoryNote.textContent=`The directory is not answering. Type an address instead.`);return}if(o.directoryList.replaceChildren(),t.length===0){o.directoryNote.textContent=`Nobody is streaming right now.`;return}o.directoryNote.textContent=`${t.length} ${t.length===1?`server is`:`servers are`} on. Connect to one to browse its files and watch what is live on it. No account needed.`;for(let e of t){let t=document.createElement(`li`),n=document.createElement(`span`);n.className=`server-label`;let r=document.createElement(`span`);r.className=`name`,r.textContent=e.name;let i=document.createElement(`span`);i.className=`detail`;let a=[`${e.tracks.toLocaleString()} files to browse`];e.playing!==!1&&e.nowPlaying?a.push(`playing ${e.nowPlaying}`):a.push(`player idle`),e.code&&a.push(e.callers?`☎ ${e.code} · ${e.callers} on the phone`:`☎ ${e.code}`),i.textContent=a.join(` · `),i.title=i.textContent,n.append(r,i);let s=e.admin??ie(e.url),c=(t,n=``)=>{te=t,g=n,o.remoteUrl.value=t?e.url:s??e.url,o.directory.hidden=!0,o.remoteForm.requestSubmit()},l=document.createElement(`ul`);l.className=`server-lives`;for(let t of e.channels??[]){let n=document.createElement(`li`),r=document.createElement(`span`);r.className=`detail live`;let i=e.channelCodes?.[t]??``,a=e.channelCallers?.[t]??0;r.textContent=`● ${t}`+(i?` · ☎ ${i}${a?` · ${a} on the phone`:``}`:``);let o=document.createElement(`button`);o.type=`button`,o.className=`button`,ht(o),o.title=`Join ${t}, live on ${e.name}`,o.addEventListener(`click`,()=>c(!0,`channel:${t}`)),n.append(r,o),l.append(n)}let[u,d]=ae({name:e.name,view:e.url,admin:s},()=>{o.directory.hidden=!0});if(t.append(n,u,d),X&&t.append(wn(e.url,e.name)),e.ownerId&&X&&e.ownerId!==X&&t.append(cr(e.ownerId,e.name)),l.childElementCount>0&&t.append(l),e.ownerId&&X&&e.ownerId===X){let n=document.createElement(`button`);n.type=`button`,n.className=`ghost`,n.textContent=`Take off the list`,n.addEventListener(`click`,t=>{t.stopPropagation(),n.disabled=!0,(async()=>{try{let t=await fetch(`/api/directory?id=${encodeURIComponent(e.id)}`,{method:`DELETE`}),n=await t.json().catch(()=>({}));o.directoryNote.textContent=t.ok?`${e.name} is off the list.`:n.error??`that did not work`}catch{o.directoryNote.textContent=`could not reach the directory`}finally{await rn()}})()}),t.append(n)}o.directoryList.append(t)}};if(document.addEventListener(`visibilitychange`,()=>{document.visibilityState===`visible`&&!o.directory.hidden&&rn(!0)}),location.pathname.replace(/\/+$/,``)===`/directory`){document.body.classList.add(`route-directory`);let e=document.getElementById(`directory-back`);e&&(e.hidden=!1),rn()}let an=null,on=e=>e===`cgnat`?`mobile or tailscale`:e===`private`?`your network`:e===`local`?`this machine`:e===`public`?`the internet`:e,sn=e=>e===`events`?`watching the panel`:e===`page`?`opened the page`:e===`media`?`playing a track`:e===`stream`?`listening live`:e,cn=``,ln=``,un=e=>{let t=e.map(e=>`${e.address}|${e.kind}|${e.track}|${Math.round(e.bytes/4096)}|${e.endedAt}`).join(`~`);if(t===cn)return;cn=t,o.adminConnections.replaceChildren();let n=document.createElement(`tr`);for(let e of[`Where`,`Network`,`Kind`,`Client`,`Track`,`Sent`]){let t=document.createElement(`th`);t.textContent=e,n.append(t)}o.adminConnections.append(n);for(let t of e.slice(0,40)){let e=document.createElement(`tr`);t.endedAt!==null&&(e.className=`ended`);let n=[[t.address,``],[on(t.network),`network-${t.network}`],[sn(t.kind),``],[t.agent,``],[t.track||`—`,``],[`${Math.round(t.bytes/1024)} KiB`,``]];for(let[t,r]of n){let n=document.createElement(`td`);n.textContent=t,r&&(n.className=r),e.append(n)}o.adminConnections.append(e)}};function W(e){o.adminSaid.textContent=e,o.adminSaid.hidden=e===``}let dn=async()=>{try{let e=await fetch(L.url(`/api/connections`));if(!e.ok)return;let t=await e.json(),n=(t.connections??[]).filter(e=>e.endedAt===null&&e.kind!==`media`&&e.kind!==`stream`).length,r=t.active??0;o.adminNote.textContent=n===0?`${r} listening now.`:`${r} listening now, and ${n} with the page open.`,un(t.connections??[]),fn(t.publish??[],(t.channels??[]).map(e=>e.id)),li(t.home??``,t.root??``),Q()}catch{o.adminNote.textContent=`lost touch with the server`}};function fn(e,t){o.publishPanel.hidden=e.length===0;let n=`${e.map(e=>`${e.id}=${e.url}`).join(`~`)}::${t.join(`,`)}`;if(n===ln)return;if(ln=n,e.length===0){o.publishList.replaceChildren();return}let r=e.length-t.length;o.publishNote.textContent=`Point OBS, Larix or ffmpeg at one of these. One publisher per URL — ${e.length} at once, ${r} free right now.`,o.publishList.replaceChildren(...e.map(e=>{let n=t.includes(e.id),r=document.createElement(`li`);n&&(r.className=`in-use`);let i=document.createElement(`span`);i.className=`slot`,i.textContent=n?`${e.id} · live`:e.id;let a=document.createElement(`input`);a.type=`text`,a.readOnly=!0,a.value=e.url,a.setAttribute(`aria-label`,`RTMP URL for ${e.id}`);let o=document.createElement(`button`);return o.type=`button`,o.className=`ghost`,o.textContent=`Copy`,o.addEventListener(`click`,()=>{a.select(),navigator.clipboard?.writeText(e.url).catch(()=>{})}),r.append(i,a,o),r}))}let pn=async()=>{if(u!==`remote`){o.adminPanel.hidden=!0,o.publishPanel.hidden=!0,an&&clearInterval(an),an=null;return}let e=!1,t=null,n=!1,r=!1;try{let i=await fetch(L.url(`/api/admin`));if(i.ok){let a=await i.json();e=a.allowed===!0,t=a.as??null,n=a.claimed===!0,r=a.member===!0}}catch{e=!1}let i=e||r;if(te&&(e=!1),ut=i&&!e,o.adminPanel.hidden=!e,Wr(e),h!==``){let e=h;h=``,dt()?Yt(e):(C=`Sign in to nixamp.com to go live here.`,V())}if(an&&clearInterval(an),an=null,Q(),Yn(),Gr(),o.listenOnly.hidden=e,!e){o.listenOnly.textContent=n?`This is a listen-only link: you can hear this server but not change what it plays. Use its control link — the first one it printed — or sign in as its owner.`:`This is a listen-only link: you can hear this server but not change what it plays. Use its control link, the first one it printed.`;return}o.adminNote.textContent=t===`owner`?`You own this server.`:`You hold this server's control link.`,dn(),an=setInterval(()=>void dn(),2e3)};function mn(e,t,n){let r=(t||e).toLowerCase().replace(/[^a-z0-9_-]+/g,`-`).replace(/^-+|-+$/g,``).slice(0,40)||`s${Math.random().toString(16).slice(2,8)}`;W(`Starting ${t||e}…`),(async()=>{try{let i=await fetch(L.url(`/api/channels/${encodeURIComponent(r)}/pull`),{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({...n===void 0?{source:e}:{at:n},...t?{name:t}:{}})}),a=await i.json();if(!i.ok){W(a.error??`that did not work`);return}W(`${a.channel?.name||t||e} is on the air.`),o.adminSource.value=``,o.adminName.value=``,Gr(),Q()}catch{W(`could not reach the server`)}})()}o.adminRestream.addEventListener(`submit`,e=>{e.preventDefault();let t=o.adminSource.value.trim();t&&mn(t,o.adminName.value.trim())}),o.adminAdd.addEventListener(`click`,()=>{let e=o.adminSource.value.trim();if(!e)return;W(`Reading ${e}…`);let t=o.adminReplace.checked,n=o.adminName.value.trim();(async()=>{try{let r=await fetch(L.url(`/api/source`),{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({source:e,...n?{name:n}:{},...t?{replace:!0}:{}})}),i=await r.json();W(r.ok?t?`Now serving ${e}.`:i.added===0?`Everything there was already in the playlist.`:`Added ${i.added??0} tracks from ${e}.`:i.error??`that did not work`),r.ok&&(o.adminSource.value=``,o.adminName.value=``,Gr(),Q())}catch{W(`could not reach the server`)}})()});let hn=e=>{let t=Math.max(1,Math.round((Date.now()-e)/6e4));if(t<60)return`${t} minute${t===1?``:`s`} ago`;let n=Math.round(t/60);return`${n} hour${n===1?``:`s`} ago`},gn=e=>{o.recentList.replaceChildren();let t=X?e.filter(e=>e.ownerId&&e.ownerId!==X):[];if(o.recentNote.hidden=t.length===0,t.length!==0)for(let e of t){let t=document.createElement(`li`),n=document.createElement(`span`);n.className=`recent-label`;let r=document.createElement(`span`);r.className=`name`,r.textContent=e.name;let i=document.createElement(`span`);i.className=`detail`,i.textContent=e.nowPlaying?`${e.nowPlaying} · ended ${hn(e.endedAt)}`:`ended ${hn(e.endedAt)}`,n.append(r,i),t.append(n,cr(e.ownerId,e.name)),o.recentList.append(t)}},_n=e=>{let t=Math.max(0,Math.floor(e)),n=String(t%60).padStart(2,`0`),r=Math.floor(t/60)%60,i=Math.floor(t/3600);return i>0?`${i}:${String(r).padStart(2,`0`)}:${n}`:`${r}:${n}`};function vn(e){let t=document.createElement(`li`),n=document.createElement(`span`);n.className=`server-label`;let r=document.createElement(`span`);r.className=`name`,r.textContent=e.event.title||e.party.partyCode;let i=document.createElement(`span`);i.className=e.party.playing?`detail live`:`detail`,i.textContent=[e.party.playing?`▶ ${_n(e.party.positionNow)}`:`❚❚ ${_n(e.party.positionNow)}`,e.party.mediaTitle,e.party.origin,e.host?`yours`:``].filter(Boolean).join(` · `),n.append(r,i);let a=document.createElement(`a`);a.className=`button`,a.href=e.links.partyUrl||e.links.nixampUrl,a.rel=`noopener`,a.target=`_blank`,a.textContent=`Watch`;let o=document.createElement(`a`);return o.className=`ghost`,o.href=e.links.nixampUrl,o.textContent=`Room`,t.append(n,a,o),t}async function yn(){if(!X){o.partiesPanel.hidden=!0;return}try{let e=await fetch(`/api/v1/watch-parties`);if(!e.ok){o.partiesPanel.hidden=!0;return}let t=(await e.json()).parties??[];o.partiesPanel.hidden=!1,o.partiesNote.textContent=t.length===0?`No parties on right now. Have a code from a site? Put it in.`:`Parties on now. Watch opens the film where it lives; Room is here.`,o.partiesList.replaceChildren(...t.map(vn))}catch{o.partiesPanel.hidden=!0}}o.partyForm.addEventListener(`submit`,e=>{e.preventDefault();let t=o.partyCode.value.trim();t!==``&&(async()=>{try{let e=await fetch(`/api/v1/watch-parties/${encodeURIComponent(t)}`),n=await e.json().catch(()=>({}));if(!e.ok){C=n.error??`no party with that code`,V();return}o.partyCode.value=``,window.location.href=n.links.nixampUrl}catch{C=`could not ask about that party`,V()}})()});async function bn(){if(!X){o.connectionsNote.hidden=!0,o.connectionsList.hidden=!0;return}try{let e=await fetch(`/api/v1/oauth/connections`);if(!e.ok){o.connectionsNote.hidden=!0,o.connectionsList.hidden=!0;return}let t=(await e.json()).connections??[];o.connectionsNote.hidden=t.length===0,o.connectionsList.hidden=t.length===0,o.connectionsList.replaceChildren(...t.map(e=>{let t=document.createElement(`li`),n=document.createElement(`span`);n.className=`server-label`;let r=document.createElement(`span`);r.className=`name`,r.textContent=e.clientName;let i=document.createElement(`span`);i.className=`detail`,i.textContent=e.scope.split(` `).filter(Boolean).join(`, `),n.append(r,i);let a=document.createElement(`button`);return a.type=`button`,a.className=`ghost`,a.textContent=`Disconnect`,a.addEventListener(`click`,()=>{(async()=>{try{await fetch(`/api/v1/oauth/connections/${encodeURIComponent(e.clientId)}`,{method:`DELETE`})}catch{}await bn()})()}),t.append(n,a),t}))}catch{o.connectionsNote.hidden=!0,o.connectionsList.hidden=!0}}let G=new Set,K=e=>{try{return new URL(e).origin}catch{return e}},xn=e=>[...G].some(t=>K(t)===K(e));async function Sn(){if(!X){G=new Set,o.favoritesPanel.hidden=!0,Tn();return}try{let e=await fetch(`/api/v1/favorites`);if(!e.ok){o.favoritesPanel.hidden=!0;return}let t=(await e.json()).favorites??[];G=new Set(t.map(e=>e.url)),o.favoritesPanel.hidden=t.length===0,o.favoritesNote.textContent=`Servers you hearted. Connect to one, or let it go.`,o.favoritesList.replaceChildren(...t.map(e=>{let t=document.createElement(`li`),n=document.createElement(`span`);n.className=`server-label`;let r=document.createElement(`span`);r.className=`name`,r.textContent=e.name||e.url.replace(/^https?:\/\//,``);let i=document.createElement(`span`);return i.className=e.live?`detail live`:`detail`,i.textContent=e.live?[`● on now`,e.nowPlaying?`playing ${e.nowPlaying}`:``,e.channels.length>0?`live: ${e.channels.join(`, `)}`:``].filter(Boolean).join(` · `):`not on right now`,n.append(r,i),t.append(n,...ae({name:e.name||e.url,view:e.url,admin:ie(e.url)}),wn(e.url,e.name)),t}))}catch{o.favoritesPanel.hidden=!0}Tn()}async function Cn(e,t,n){try{if(!(n?await fetch(`/api/v1/favorites`,{method:`PUT`,headers:{"content-type":`application/json`},body:JSON.stringify({url:e,name:t})}):await fetch(`/api/v1/favorites?url=${encodeURIComponent(e)}`,{method:`DELETE`})).ok){C=n?`Could not save that favourite.`:`Could not remove that favourite.`,V();return}}catch{C=`could not reach nixamp.com`,V();return}if(n)G.add(e);else for(let t of[...G])K(t)===K(e)&&G.delete(t);await Sn()}function wn(e,t){let n=document.createElement(`button`);n.type=`button`,n.className=`heart`;let r=()=>{let t=xn(e);n.textContent=t?`♥`:`♡`,n.dataset.on=t?`yes`:`no`,n.title=t?`Remove from favourites`:`Add to favourites`,n.setAttribute(`aria-label`,n.title)};return r(),n.addEventListener(`click`,n=>{n.stopPropagation();let i=[...G].find(t=>K(t)===K(e))??e;Cn(xn(e)?i:e,t,!xn(e)).then(r)}),n}function Tn(){let e=u===`remote`?L.shareLink:``;if(o.favHere.hidden=!(X&&e),o.favHere.hidden)return;let t=xn(e);o.favHere.textContent=t?`♥`:`♡`,o.favHere.dataset.on=t?`yes`:`no`,o.favHere.title=t?`Remove this server from your favourites`:`Add this server to your favourites`,o.favHere.setAttribute(`aria-label`,o.favHere.title)}c(o.copyNow,`copy`),o.copyNow.addEventListener(`click`,()=>{ri(I.source,o.copyNow,`✓`)});function En(e){if(e===``||e===st)return;st=e;let t=document.createElement(`li`),n=document.createElement(`time`),r=new Date;n.dateTime=r.toISOString(),n.textContent=r.toLocaleTimeString([],{hour:`2-digit`,minute:`2-digit`,second:`2-digit`});let i=document.createElement(`span`);for(i.textContent=e,t.append(n,i),o.logList.prepend(t);o.logList.children.length>100;)o.logList.lastElementChild?.remove()}let Dn=3e3,On=/(^|\.)nixamp\.com$/.test(globalThis.location.hostname)?``:`https://nixamp.com`,kn=``,An=``,jn=null,Mn=!1,Nn=new Set;function Pn(){if(u!==`remote`)return null;let{base:e}=pe(ai());if(e===``)return null;let t=``;try{t=new URL(e).origin}catch{return null}let n=T?T.id:D?.kind===`live`?`live`:``;return n===``?null:{server:t,channel:n}}function Fn(e,t=``){let n=new URLSearchParams({server:e.server,channel:e.channel});return t&&n.set(`after`,t),`${On}/api/v1/trollbox?${n.toString()}`}function In(){let e=Pn(),t=e?`${e.server}|${e.channel}`:``;t!==kn&&(kn=t,An=``,Nn.clear(),o.trollboxList.replaceChildren(),jn&&clearTimeout(jn),jn=null,o.trollboxPanel.hidden=e===null,o.trollboxForm.hidden=!0,e&&(o.trollboxNote.textContent=`The room for ${z()||e.channel}. Loading…`,Rn()))}function Ln(e){let t=document.createElement(`li`);t.dataset.id=e.id;let n=document.createElement(`time`);n.className=`when`,n.dateTime=e.createdAt;let r=new Date(e.createdAt);n.textContent=Number.isNaN(r.getTime())?``:r.toLocaleTimeString([],{hour:`2-digit`,minute:`2-digit`});let i=document.createElement(`span`);i.className=`who`,i.textContent=e.handle;let a=document.createElement(`span`);if(a.className=`line`,a.textContent=e.body,t.append(n,i,a),e.mine===!0||lt()){let n=document.createElement(`button`);n.type=`button`,n.className=`icon-btn`,n.textContent=`✕`,n.title=e.mine===!0?`Take your line down`:`Take this line down`,n.setAttribute(`aria-label`,n.title),n.addEventListener(`click`,()=>{zn(e.id,t)}),t.append(n)}return t}async function Rn(){let e=Pn(),t=e?`${e.server}|${e.channel}`:``;if(e&&t===kn&&!Mn){Mn=!0;try{let n=await fetch(Fn(e,An)),r=await n.json().catch(()=>({}));if(t!==kn)return;if(!n.ok){o.trollboxNote.textContent=r.error??`The trollbox is not answering.`;return}let i=(r.messages??[]).filter(e=>!Nn.has(e.id));for(let e of i)Nn.add(e.id),o.trollboxList.append(Ln(e)),An=e.createdAt;for(;o.trollboxList.children.length>200;)o.trollboxList.firstElementChild?.remove();i.length>0&&o.trollboxList.lastElementChild?.scrollIntoView({block:`nearest`});let a=r.you??``;o.trollboxForm.hidden=a===``,o.trollboxNote.textContent=a===``?On===``?`The room for ${z()||e.channel}. Sign in to say something.`:`The room for ${z()||e.channel}. To say something, open this stream on nixamp.com and sign in.`:`You are ${a} in the room for ${z()||e.channel}.`}catch{t===kn&&(o.trollboxNote.textContent=`The trollbox is not answering.`)}finally{Mn=!1,t===kn&&(jn=setTimeout(()=>{document.visibilityState===`visible`?Rn():jn=setTimeout(()=>void Rn(),Dn)},Dn))}}}async function zn(e,t){let n=Pn();if(n)try{let r=await fetch(`${On}/api/v1/trollbox/${encodeURIComponent(e)}?${new URLSearchParams(n).toString()}`,{method:`DELETE`});r.ok?t.remove():o.trollboxNote.textContent=(await r.json().catch(()=>({}))).error??`that did not work`}catch{o.trollboxNote.textContent=`The trollbox is not answering.`}}o.trollboxForm.addEventListener(`submit`,e=>{e.preventDefault();let t=Pn(),n=o.trollboxInput.value.trim();t&&n!==``&&(o.trollboxInput.disabled=!0,(async()=>{try{let e=await fetch(`${On}/api/v1/trollbox`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({server:t.server,channel:t.channel,body:n})}),r=await e.json().catch(()=>({}));if(!e.ok||!r.message){o.trollboxNote.textContent=r.error??`that did not send`;return}o.trollboxInput.value=``,Nn.has(r.message.id)||(Nn.add(r.message.id),o.trollboxList.append(Ln(r.message)),An=r.message.createdAt,o.trollboxList.lastElementChild?.scrollIntoView({block:`nearest`}))}catch{o.trollboxNote.textContent=`The trollbox is not answering.`}finally{o.trollboxInput.disabled=!1,o.trollboxInput.focus()}})())});function Bn(){if(k)return k.url;if(u!==`remote`)return``;let{base:e,key:t}=pe(ai());if(e===``)return``;if(T)return x(e,`/api/channels/${encodeURIComponent(T.id)}`,t);if(D?.kind===`live`)return x(e,`/api/live`,t);if(I.source===``)return``;try{let n=new URL(I.source);return n.origin===new URL(e).origin?(t?n.searchParams.set(`k`,t):n.searchParams.delete(`k`),n.toString()):``}catch{return``}}function Vn(){let e=Bn();return e===``?``:`https://nixamp.com/?play=${encodeURIComponent(e)}`}o.shareNow.addEventListener(`click`,()=>{let e=Vn();if(e===``)return;let t=navigator;if(typeof t.share==`function`){t.share({title:`${z()} on nixamp`,url:e}).catch(()=>void 0);return}ri(e,o.shareNow,`Copied`)}),o.favHere.addEventListener(`click`,()=>{let e=ai()||L.shareLink;if(!e)return;let t=[...G].find(t=>K(t)===K(e))??e;Cn(xn(e)?t:e,d||L.address,!xn(e)).then(Tn)});let Hn=r?a:200,q=[],J=null,Y=null,Un=``,Wn=[],Gn=0,Kn=null,qn=0;function Jn(e){if(!e)return`never`;let t=Math.max(0,Math.round((Date.now()-e)/1e3));if(t<90)return`just now`;let n=Math.round(t/60);if(n<90)return`${n} min ago`;let r=Math.round(n/60);return r<36?`${r} h ago`:`${Math.round(r/24)} d ago`}async function Yn(){if(u!==`remote`){o.catalogsPanel.hidden=!0;return}let e;try{e=await fetch(L.url(`/api/catalogs`))}catch{o.catalogsPanel.hidden=!0;return}if(!e.ok){o.catalogsPanel.hidden=!0;return}q=(await e.json().catch(()=>({}))).catalogs??[],J&&=q.find(e=>e.id===J?.id)??null,J||(Y=null),o.catalogsPanel.hidden=!1,Xn()}function Xn(){let e=!o.adminPanel.hidden;o.catalogsForm.hidden=!e;let t=q.reduce((e,t)=>e+t.live,0),n=q.reduce((e,t)=>e+t.vod,0);o.catalogsNote.textContent=q.length===0?e?`No catalogs yet. Add an m3u list of channels or films.`:`No catalogs yet.`:`${q.length} ${q.length===1?`catalog`:`catalogs`} · ${t} live ${t===1?`channel`:`channels`} · ${n} on demand`,Zn();let r=J!==null&&Y!==null;if(o.catalogsList.hidden=r,o.catalogsFilter.hidden=!r,o.catalogsEntries.hidden=!r,r){nr();return}if(J){$n(J);return}o.catalogsList.replaceChildren(...q.map(t=>Qn(t,e)))}function Zn(){let e=J!==null;if(o.catalogsCrumbs.hidden=!e,!e)return;let t=(e,t,n)=>{if(n){let t=document.createElement(`span`);return t.className=`here`,t.textContent=e,t}let r=document.createElement(`button`);return r.type=`button`,r.textContent=e,r.addEventListener(`click`,t),r},n=()=>{let e=document.createElement(`span`);return e.textContent=`/`,e},r=[t(`All catalogs`,()=>{J=null,Y=null,Xn()},!1),n(),t(J?.name??``,()=>{Y=null,Xn()},Y===null)];Y!==null&&r.push(n(),t(Y===``?`All groups`:Y,()=>void 0,!0)),o.catalogsCrumbs.replaceChildren(...r)}function Qn(e,t){let n=document.createElement(`li`),r=document.createElement(`span`);r.className=`server-label`;let i=document.createElement(`span`);i.className=`name`,i.textContent=e.name;let a=document.createElement(`span`);if(a.className=`detail`,a.textContent=[`${e.entries.toLocaleString()} ${e.entries===1?`entry`:`entries`}`,`${e.live.toLocaleString()} live`,`${e.vod.toLocaleString()} on demand`,`refreshed ${Jn(e.refreshedAt)}`].join(` · `),r.append(i,a),t&&e.error){let t=document.createElement(`span`);t.className=`detail`,t.textContent=e.error,r.append(t)}let o=document.createElement(`button`);if(o.type=`button`,o.className=`button`,o.textContent=`Browse`,o.addEventListener(`click`,()=>{J=e,Y=null,Xn()}),n.append(r,o),t){let t=document.createElement(`button`);t.type=`button`,t.className=`ghost`,t.textContent=`Refresh`,t.title=`Read the list again`,t.addEventListener(`click`,()=>{ir(e)});let r=document.createElement(`button`);r.type=`button`,r.className=`ghost`,r.textContent=`Remove`,r.title=`Take this catalog off the server`,r.addEventListener(`click`,()=>{confirm(`Remove ${e.name} from this server?`)&&ar(e)}),n.append(t,r)}return n}async function $n(e){o.catalogsList.replaceChildren();let t=[];try{let n=await E(()=>fetch(L.url(`/api/catalogs/${encodeURIComponent(e.id)}/groups`)));if(!n.ok)throw Error(String(n.status));t=(await n.json()).groups??[]}catch{C=`Could not read the groups in ${e.name}.`,V();return}if(J?.id!==e.id||Y!==null)return;let n=[er(`All groups`,``,e.entries,e.live,e.vod),...t.map(e=>er(e.name||`(no group)`,e.name,e.count,e.live,e.vod))];o.catalogsList.replaceChildren(...n)}function er(e,t,n,r,i){let a=document.createElement(`li`),s=document.createElement(`span`);s.className=`server-label`;let c=document.createElement(`span`);c.className=`name`,c.textContent=e;let l=document.createElement(`span`);l.className=`detail`,l.textContent=`${n.toLocaleString()} · ${r.toLocaleString()} live · ${i.toLocaleString()} on demand`,s.append(c,l);let u=document.createElement(`button`);return u.type=`button`,u.className=`button`,u.textContent=`Open`,u.addEventListener(`click`,()=>{Y=t,Un=``,o.catalogsFilter.value=``,Wn=[],Gn=0,Xn(),tr(0)}),a.append(s,u),a}async function tr(e){let t=J,n=Y;if(!t||n===null)return;let r=++qn,i=new URLSearchParams({group:n,q:Un,offset:String(e),limit:String(Hn)}),a;try{let e=await E(()=>fetch(L.url(`/api/catalogs/${encodeURIComponent(t.id)}/entries?${i}`)));if(!e.ok)throw Error(String(e.status));a=await e.json()}catch{C=`Could not read ${t.name}.`,V();return}r===qn&&(Gn=a.total??0,Wn=e===0?a.entries??[]:[...Wn,...a.entries??[]],nr())}function nr(){let t=J;if(!t)return;let n=Wn.map(n=>{let r=document.createElement(`li`);r.className=`row`;let i=document.createElement(`span`);i.className=`name`,i.textContent=n.title;let a=document.createElement(`span`);if(a.className=n.live?`catalog-tag catalog-live`:`catalog-tag`,a.textContent=n.live?`LIVE`:n.duration>0?e(n.duration):`VOD`,r.append(i,a),!n.live){let e=document.createElement(`button`);e.type=`button`,e.className=`row-copy`,c(e,`copy`),e.title=`Copy this entry's URL`,e.setAttribute(`aria-label`,`Copy the URL of ${n.title}`),e.addEventListener(`click`,r=>{r.stopPropagation();let i=`/api/catalogs/${encodeURIComponent(t.id)}/entries/${encodeURIComponent(n.id)}/stream`;ri(L.url(i),e,`✓`)}),r.append(e)}return dt()&&r.append(gt(()=>({kind:`entry`,catalog:{id:t.id,name:t.name},entry:n}),n.title)),r.addEventListener(`click`,()=>{rr(t,n,r)}),r});if(Wn.length===0){let e=document.createElement(`li`);e.className=`group`;let t=document.createElement(`span`);t.className=`group-name`,t.textContent=Un?`Nothing called "${Un}" here.`:`Nothing in this group.`,e.append(t),n.push(e)}else if(Wn.length<Gn){let e=document.createElement(`li`);e.className=`group`;let t=document.createElement(`button`);t.type=`button`,t.className=`ghost`,t.textContent=`Show more (${Wn.length.toLocaleString()} of ${Gn.toLocaleString()})`,t.addEventListener(`click`,e=>{e.stopPropagation(),tr(Wn.length)}),e.append(t),n.push(e)}o.catalogsEntries.replaceChildren(...n)}async function rr(e,t,n){n?.classList.add(`loading`),C=`Starting ${t.title}…`;let r={catalog:{id:e.id,name:e.name},entry:t};try{await E(async()=>{let n,i={};try{n=await fetch(L.url(`/api/catalogs/${encodeURIComponent(e.id)}/entries/${encodeURIComponent(t.id)}/play`),{method:`POST`}),i=await n.json().catch(()=>({}))}catch{C=`could not reach the server`;return}if(!n.ok){C=i.error??`${t.title} would not play.`;return}let a=i.name||t.title;if(i.kind===`live`&&i.channel){await Qr({id:i.channel,name:a,video:!0},!0,{kind:`channel`,...r});return}if(i.kind===`vod`&&i.url){T=null,w=-1,D={kind:`vod`,...r},Ge(a,`title`),await I.load({title:a,artist:``,album:``,duration:0,url:L.url(i.url),video:!0,objectUrl:!1},!0),U(!0),C=`Playing ${a}.`;return}C=`${t.title} would not play.`})}finally{n?.classList.remove(`loading`),V()}}async function ir(e){W(`Reading ${e.name} again…`);try{let t=await fetch(L.url(`/api/catalogs/${encodeURIComponent(e.id)}/refresh`),{method:`POST`}),n=await t.json().catch(()=>({}));W(t.ok?`${n.catalog?.name??e.name}: ${(n.catalog?.entries??0).toLocaleString()} entries.`:n.error??`that did not work`)}catch{W(`could not reach the server`)}Yn()}async function ar(e){try{W((await fetch(L.url(`/api/catalogs/${encodeURIComponent(e.id)}`),{method:`DELETE`})).ok?`${e.name} is off the server.`:`that did not work`)}catch{W(`could not reach the server`)}J?.id===e.id&&(J=null,Y=null),Yn()}o.catalogsForm.addEventListener(`submit`,e=>{e.preventDefault();let t=o.catalogSource.value.trim(),n=o.catalogName.value.trim();t&&(async()=>{W(`Reading ${n||t}…`);try{let e=await fetch(L.url(`/api/catalogs`),{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({source:t,name:n})}),r=await e.json().catch(()=>({}));if(!e.ok){W(r.error??`that did not work`);return}W(`${r.catalog?.name??n??t}: ${(r.catalog?.entries??0).toLocaleString()} entries.`),o.catalogSource.value=``,o.catalogName.value=``}catch{W(`could not reach the server`)}Yn()})()}),o.catalogsFilter.addEventListener(`input`,()=>{Kn&&clearTimeout(Kn),Kn=setTimeout(()=>{Kn=null,Un=o.catalogsFilter.value.trim(),tr(0)},250)});let or=async()=>{o.serversList.replaceChildren();try{let e=await fetch(`/api/v1/servers`);if(!e.ok){o.serversPanel.hidden=!0;return}let t=(await e.json()).servers??[];ne=new Map(t.map(e=>[K(e.url),e.key?`${e.url}/admin/${e.key}`:e.url])),o.serversPanel.hidden=!1,o.serversNote.textContent=t.length===0?"No servers yet. `nixamp server add --here` remembers the one you are running.":`The machines on your account. View one, administer it, or forget it.`;for(let e of t){let t=document.createElement(`li`),n=document.createElement(`span`);n.className=`recent-label`;let r=document.createElement(`span`);r.className=`name`,r.textContent=e.name;let i=document.createElement(`span`);i.className=`detail`,i.textContent=e.url,n.append(r,i);let a=e.key?`${e.url}/admin/${e.key}`:e.url,[s,c]=ae({name:e.name,view:a,admin:a});ye(e.url).then(n=>{if(n!==null){i.textContent=`${e.url} · ${n}`;return}i.textContent=`${e.url} · not answering`,t.classList.add(`offline`),s.disabled=!0,c.disabled=!0,s.title=c.title=`That machine is not answering. Start nixamp on it.`});let l=document.createElement(`button`);l.type=`button`,l.className=`ghost`,l.textContent=`Forget`,l.addEventListener(`click`,()=>{(async()=>{l.disabled=!0;try{await fetch(`/api/v1/servers/${encodeURIComponent(e.id)}`,{method:`DELETE`}),await or()}catch{l.disabled=!1}})()}),t.append(n,s,c,l),o.serversList.append(t)}}catch{o.serversPanel.hidden=!0}},sr=async()=>{o.followingList.replaceChildren();try{let e=await fetch(`/api/v1/follows`);if(!e.ok){o.followingNote.hidden=!0;return}let t=(await e.json()).following??[];o.followingNote.hidden=t.length===0;for(let e of t){let t=document.createElement(`li`),n=document.createElement(`span`);n.className=`recent-label`;let r=document.createElement(`span`);r.className=`name`,r.textContent=e.name||`a nixamp`;let i=document.createElement(`span`);i.className=`detail`,i.textContent=e.live?`live now`:`not streaming`,n.append(r,i);let a=document.createElement(`button`);a.type=`button`,a.className=`ghost follow`,a.textContent=`Unfollow`,a.addEventListener(`click`,()=>{(async()=>{a.disabled=!0;try{await fetch(`/api/v1/follows/${encodeURIComponent(e.id)}`,{method:`DELETE`}),t.remove(),o.followingList.children.length===0&&(o.followingNote.hidden=!0)}finally{a.disabled=!1}})()}),t.append(n,a),o.followingList.append(t)}}catch{o.followingNote.hidden=!0}},cr=(e,t)=>{let n=document.createElement(`button`);n.type=`button`,n.className=`ghost follow`,n.textContent=`Follow`,n.setAttribute(`aria-label`,`Follow ${t}`);let r=e=>{n.textContent=e?`Following`:`Follow`,n.dataset.following=e?`yes`:`no`};return(async()=>{try{let t=await fetch(`/api/v1/follows/${encodeURIComponent(e)}`);t.ok&&r((await t.json()).following===!0)}catch{}})(),n.addEventListener(`click`,()=>{(async()=>{let t=n.dataset.following===`yes`;n.disabled=!0;try{(await fetch(`/api/v1/follows/${encodeURIComponent(e)}`,{method:t?`DELETE`:`PUT`,headers:{"content-type":`application/json`},body:t?void 0:`{}`})).ok&&(r(!t),sr())}catch{}finally{n.disabled=!1}})()}),n},lr=e=>{let t=(e+`=`.repeat((4-e.length%4)%4)).replace(/-/g,`+`).replace(/_/g,`/`),n=atob(t),r=new Uint8Array(new ArrayBuffer(n.length));for(let e=0;e<n.length;e+=1)r[e]=n.charCodeAt(e);return r},ur=()=>`serviceWorker`in navigator&&`PushManager`in window&&`Notification`in window,dr=async()=>{if(!ur())return o.notifyNote.textContent=`This browser cannot show notifications.`,!1;if(Notification.permission===`denied`)return o.notifyNote.textContent=`This browser is blocking notifications. Allow them in site settings first.`,!1;if(await Notification.requestPermission()!==`granted`)return o.notifyNote.textContent=`Not allowed, so nothing will be sent here.`,!1;try{let e=await navigator.serviceWorker.ready,{publicKey:t}=await(await fetch(`/api/v1/notify/key`)).json();if(!t)return o.notifyNote.textContent=`This server is not set up to send notifications.`,!1;let n=await e.pushManager.getSubscription()??await e.pushManager.subscribe({userVisibleOnly:!0,applicationServerKey:lr(t)}),r=await fetch(`/api/v1/notify/subscribe`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify(n.toJSON())});if(!r.ok)throw Error(String(r.status));return o.notifyNote.textContent=`This device will be told.`,!0}catch{return o.notifyNote.textContent=`Could not set this device up.`,!1}},fr=async()=>{try{let e=await(await navigator.serviceWorker.ready).pushManager.getSubscription();if(!e)return;await fetch(`/api/v1/notify/subscribe?endpoint=${encodeURIComponent(e.endpoint)}`,{method:`DELETE`}),await e.unsubscribe()}catch{}},pr=async e=>{try{let t=await fetch(`/api/v1/notify/prefs`,{method:`PUT`,headers:{"content-type":`application/json`},body:JSON.stringify(e)}),n=await t.json();o.notifyPhoneNote.textContent=t.ok?``:n.error??`that did not save`,t.ok&&typeof n.phone==`string`&&(o.notifyPhone.value=n.phone)}catch{o.notifyPhoneNote.textContent=`could not reach nixamp.com`}},mr=async()=>{try{let e=await fetch(`/api/v1/notify/prefs`);if(!e.ok)return;let t=await e.json();o.notifyEmail.checked=t.wantsEmail!==!1,o.notifySms.checked=t.wantsSms===!0,o.notifyPhone.value=t.phone??``;let n=ur()&&Notification.permission===`granted`?await(await navigator.serviceWorker.ready).pushManager.getSubscription()!==null:!1;o.notifyWeb.checked=t.wantsWeb!==!1&&n,o.notifyNote.textContent=n?`Get told when someone you follow goes live.`:`Turn on “On this device” to be told here.`}catch{}};o.notifyWeb.addEventListener(`change`,()=>{(async()=>{if(o.notifyWeb.checked){let e=await dr();o.notifyWeb.checked=e,await pr({wantsWeb:e});return}await fr(),await pr({wantsWeb:!1}),o.notifyNote.textContent=`Turn on “On this device” to be told here.`})()}),o.notifyEmail.addEventListener(`change`,()=>{pr({wantsEmail:o.notifyEmail.checked})}),o.notifySms.addEventListener(`change`,()=>{(async()=>{if(o.notifySms.checked&&!o.notifyPhone.value.trim()){o.notifyPhoneNote.textContent=`Add a phone number first.`,o.notifySms.checked=!1,o.notifyPhone.focus();return}await pr({wantsSms:o.notifySms.checked})})()}),o.notifyPhoneForm.addEventListener(`submit`,e=>{e.preventDefault(),pr({phone:o.notifyPhone.value.trim()})});let hr=!1,X=``,gr=!1,_r=`nixamp.welcome`,vr=()=>{let e=!1;try{e=localStorage.getItem(_r)===`hidden`}catch{}o.welcome.hidden=!gr||X!==``||e},yr=e=>{let t=e!==null;o.notifyPanel.hidden=!t,t?(mr(),sr(),or()):(o.serversPanel.hidden=!0,o.followingNote.hidden=!0,o.followingList.replaceChildren(),o.recentNote.hidden=!0,o.recentList.replaceChildren()),o.accountForm.hidden=t,o.accountProviders.hidden=t||o.accountProviders.childElementCount===0,o.accountSignOut.hidden=!t,o.accountNote.textContent=t?`Signed in as ${e}.`:hr?`Create an account on nixamp.com.`:`Listening needs no account. Sign in to keep favourites, follow people, and publish.`,o.accountSubmit.textContent=hr?`Create account`:`Sign in`,o.accountToggle.textContent=hr?`I have one`:`Create one`,o.accountPassword.autocomplete=hr?`new-password`:`current-password`,vr()},br=async()=>{let e=[];gr=!1;try{let t=await fetch(`/api/v1/auth/providers`);t.ok&&(gr=!0,e=(await t.json()).providers??[])}catch{}o.accountProviders.replaceChildren(),o.accountProviders.hidden=e.length===0,o.accountPanel.hidden=!gr,o.accountElsewhere.hidden=gr,vr();for(let t of e){let e=document.createElement(`a`);e.className=`button`,e.href=`/api/v1/${encodeURIComponent(t.id)}/oauth/start`,e.textContent=`Continue with ${t.name}`,o.accountProviders.append(e)}},xr=async()=>{try{let e=await fetch(`/api/v1/auth/me`),t=await e.json();X=e.ok?t.account?.id??``:``,yr(e.ok?t.account?.email??`you`:null)}catch{X=``,yr(null)}if(L.session=``,X!==``){try{let e=await fetch(`/api/v1/auth/token`),t=await e.json().catch(()=>({}));L.session=e.ok&&typeof t.token==`string`?t.token:``}catch{L.session=``}u===`remote`&&pn()}Sn(),yn(),bn(),Sr()};function Sr(){if($e===``)return;let e=$e;$e=``,o.remoteUrl.value=e,o.remoteForm.requestSubmit()}o.accountToggle.addEventListener(`click`,()=>{hr=!hr,yr(null)}),o.welcomeCreate.addEventListener(`click`,()=>{hr=!0,yr(null),o.accountPanel.scrollIntoView({behavior:`smooth`,block:`center`}),o.accountEmail.focus({preventScroll:!0})}),o.welcomeBrowse.addEventListener(`click`,()=>{o.directory.hidden?o.browse.click():o.directory.scrollIntoView({behavior:`smooth`,block:`nearest`})}),o.welcomeHide.addEventListener(`click`,()=>{try{localStorage.setItem(_r,`hidden`)}catch{}o.welcome.hidden=!0}),o.accountForm.addEventListener(`submit`,e=>{e.preventDefault();let t=o.accountEmail.value.trim(),n=o.accountPassword.value;(async()=>{o.accountSubmit.disabled=!0;try{let e=await fetch(`/api/v1/auth/${hr?`signup`:`login`}`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({email:t,password:n})}),r=await e.json();if(!e.ok){o.accountNote.textContent=r.error??`that did not work`;return}X=r.account?.id??``,o.accountPassword.value=``,yr(r.account?.email??t),pn(),Sr()}catch{o.accountNote.textContent=`could not reach nixamp.com`}finally{o.accountSubmit.disabled=!1}})()}),o.accountSignOut.addEventListener(`click`,()=>{(async()=>{try{await fetch(`/api/v1/auth/logout`,{method:`POST`})}catch{}X=``,yr(null),L.close(),w=-1,u=`local`,b=`idle`,S=``,o.remoteUrl.value=``,o.sharePanel.hidden=!0,o.publishPanel.hidden=!0,o.adminPanel.hidden=!0,o.onairPanel.hidden=!0,o.catalogsPanel.hidden=!0,o.listenOnly.hidden=!0,Jr(!1);try{localStorage.removeItem(nt)}catch{}C=`Signed out, and disconnected from the server.`,pn(),V()})()});try{let e=new URL(globalThis.location.href).searchParams,t=e.get(`url`)??``;t!==``&&($e=t,g=e.get(`play`)??``,_=Math.max(0,Number(e.get(`t`)??`0`)||0),o.remoteUrl.value=t,C=`Opening the stream you were sent…`,globalThis.history?.replaceState(null,``,globalThis.location.pathname));let n=e.get(`link`)??``;n!==``&&(o.linkUrl.value=n,t!==``&&g===``?g=`link:${n}`:t===``&&(et=n));let r=e.get(`play`)??``;if(t===``&&/^https?:\/\//i.test(r)){let t=fe(r);t?($e=t.view,g=t.what,_=Math.max(0,Number(e.get(`t`)??`0`)||0),o.remoteUrl.value=t.view,C=`Opening the stream you were sent…`):(o.linkUrl.value=r,et=r),globalThis.history?.replaceState(null,``,globalThis.location.pathname)}}catch{}et!==``&&Gt(et);let Cr=[`zone-top`,`zone-main`],wr=e=>e instanceof HTMLElement&&e.matches(`section.panel`)&&e.id!==``,Tr=e=>e.parentElement&&Cr.includes(e.parentElement.id)?e.parentElement:null,Er=e=>e.classList.contains(`col-a`)?`a`:e.classList.contains(`col-b`)?`b`:``,Dr=()=>[...document.querySelectorAll(`section.panel`)].filter(e=>wr(e)&&Tr(e)!==null),Or=e=>e.dataset.title??e.id,kr=new Map,Ar=Dr().map((e,t)=>(kr.set(e.id,{zone:Tr(e)?.id??``,col:Er(e),index:t}),e.id)),Z=Ue();try{Z=qe(localStorage.getItem(He))}catch{}let jr=()=>Z.order.length>0||Object.keys(Z.placement).length>0;function Mr(){Z.order=Dr().map(e=>e.id);try{localStorage.setItem(He,Je(Z))}catch{}}function Nr(e,t,n){e.classList.remove(`col-a`,`col-b`),t.classList.contains(`split`)&&n!==``&&e.classList.add(`col-${n}`),t!==e.parentElement&&t.append(e)}function Pr(){for(let e of Dr()){let t=Z.placement[e.id];if(!t)continue;let[n=``,r=``]=t.split(`:`),i=document.getElementById(n);i&&Cr.includes(i.id)&&Nr(e,i,r===`a`||r===`b`?r:``)}let e=Ye(Dr().map(e=>e.id),Z.order);for(let t of Cr){let n=document.getElementById(t);if(!n)continue;let r=[...n.children].filter(wr),i=r.map(e=>{let t=document.createComment(`panel`);return e.before(t),t});r.sort((t,n)=>e.indexOf(t.id)-e.indexOf(n.id)),r.forEach((e,t)=>i[t]?.replaceWith(e))}for(let e of Dr())e.toggleAttribute(`data-collapsed`,Z.collapsed.includes(e.id)),e.toggleAttribute(`data-closed`,Z.closed.includes(e.id))}function Fr(e,t){e.toggleAttribute(`data-collapsed`,t),Z.collapsed=Xe(Z.collapsed,e.id,t),Mr(),Hr()}function Ir(e,t){e.toggleAttribute(`data-closed`,t),Z.closed=Xe(Z.closed,e.id,t),Mr(),Hr()}function Lr(e,t,n){let r=Tr(t);r&&e!==t&&(Nr(e,r,Er(t)),n?t.after(e):t.before(e),Z.placement[e.id]=`${r.id}:${Er(t)}`,Mr(),Hr())}let Rr=null,zr=()=>{for(let e of Dr())e.classList.remove(`drop-before`,`drop-after`)},Br=(e,t,n)=>{let r=document.createElement(`button`);return r.type=`button`,r.className=`icon-btn`,r.textContent=e,r.title=t,r.setAttribute(`aria-label`,t),r.addEventListener(`click`,e=>{e.stopPropagation(),n()}),r};function Vr(){for(let e of Dr()){if(e.querySelector(`:scope > .panel-tools`))continue;let t=document.createElement(`span`);t.className=`panel-tools`;let n=Br(`≡`,`Move ${Or(e)}: drag it to where it should go`,()=>void 0);n.classList.add(`grip`),n.draggable=!0,n.addEventListener(`dragstart`,t=>{Rr=e,e.classList.add(`dragging`),t.dataTransfer?.setData(`text/plain`,e.id),t.dataTransfer&&(t.dataTransfer.effectAllowed=`move`)}),n.addEventListener(`dragend`,()=>{Rr=null,e.classList.remove(`dragging`),zr()});let r=Br(`▁`,`Shade ${Or(e)} to its title`,()=>Fr(e,!e.hasAttribute(`data-collapsed`))),i=Br(`✕`,`Hide ${Or(e)}; the Panels list turns it back on`,()=>Ir(e,!0));t.append(n,r,i),e.prepend(t),e.addEventListener(`dragover`,t=>{if(!Rr||Rr===e)return;t.preventDefault();let n=e.getBoundingClientRect(),r=t.clientY>n.top+n.height/2;zr(),e.classList.add(r?`drop-after`:`drop-before`)}),e.addEventListener(`dragleave`,()=>e.classList.remove(`drop-before`,`drop-after`)),e.addEventListener(`drop`,t=>{if(!Rr||Rr===e)return;t.preventDefault();let n=e.classList.contains(`drop-after`);zr(),Lr(Rr,e,n)})}}function Hr(){if(o.panelsPanel.hidden)return;let e=Dr().filter(e=>e!==o.panelsPanel).map(e=>{let t=document.createElement(`li`),n=e.hasAttribute(`data-closed`);t.classList.toggle(`off`,n);let r=document.createElement(`label`),i=document.createElement(`input`);i.type=`checkbox`,i.checked=!n,i.setAttribute(`aria-label`,`${Or(e)} on`),i.addEventListener(`change`,()=>Ir(e,!i.checked));let a=document.createElement(`span`);a.className=`name`,a.textContent=Or(e),r.append(i,a);let o=document.createElement(`span`);return o.className=`detail`,o.textContent=n?`off`:e.hasAttribute(`data-collapsed`)?`shaded`:e.hidden?`nothing to show right now`:``,t.append(r,o),t});o.panelsList.replaceChildren(...e)}function Ur(){Z=Ue();for(let e of Ar){let t=document.getElementById(e),n=kr.get(e),r=n?document.getElementById(n.zone):null;t&&n&&r&&(Nr(t,r,n.col),t.removeAttribute(`data-collapsed`),t.removeAttribute(`data-closed`))}Z.order=[...Ar],Pr(),Z=Ue();try{localStorage.removeItem(He)}catch{}Hr()}function Wr(e){if(!e||jr())return;let t=Tr(o.adminPanel);if(!t)return;let n=[...t.children].filter(wr).find(e=>Er(e)===Er(o.adminPanel));n&&n!==o.adminPanel&&n.before(o.adminPanel),o.adminPanel.removeAttribute(`data-collapsed`),o.adminPanel.removeAttribute(`data-closed`)}o.panelsToggle.addEventListener(`click`,()=>{let e=o.panelsPanel.hidden;o.panelsPanel.hidden=!e,e&&(o.panelsPanel.hasAttribute(`data-closed`)&&Ir(o.panelsPanel,!1),Hr(),o.panelsPanel.scrollIntoView({behavior:`smooth`,block:`nearest`}))}),o.panelsReset.addEventListener(`click`,Ur),new MutationObserver(()=>Hr()).observe(document.body,{attributes:!0,attributeFilter:[`data-title`,`hidden`],subtree:!0}),Vr(),Pr(),br(),xr(),pn(),Zt(),o.browse.addEventListener(`click`,()=>{if(!o.directory.hidden){o.directory.hidden=!0;return}rn(),o.directory.scrollIntoView({behavior:`smooth`,block:`nearest`})}),o.disconnect.addEventListener(`click`,()=>{L.close(),o.listenOnly.hidden=!0,w=-1,o.sharePanel.hidden=!0,o.publishPanel.hidden=!0,o.adminPanel.hidden=!0,o.onairPanel.hidden=!0,o.onairPanel.dataset.title=`Live on this server`,o.catalogsPanel.hidden=!0,o.catalogsPanel.dataset.title=`Catalogs on this server`,d=``,se=``,Tn(),Jr(!1),u=`local`,b=`idle`,S=``,V()});async function Gr(){if(u!==`remote`||L.shareLink===``){o.sharePanel.hidden=!0;return}o.sharePanel.hidden=!1;let e=ai(),t=globalThis.location.origin;o.shareLink.value=e===``?``:e.startsWith(`https://`)?`${t}/?url=${encodeURIComponent(e)}`:e,o.shareNote.textContent=`Anyone with this link can watch. They sign in once, then it opens.`,o.sharePhone.hidden=!0,o.shareSend.hidden=!0,o.liveControls.hidden=!0;let n=``;try{let e=await fetch(`/api/directory`);e.ok&&(n=(await e.json()).callIn??``)}catch{}let r=null;try{let n=await fetch(L.url(`/api/live/state`));n.ok&&(r=await n.json()),r?.url&&(e=r.url,se=r.url,o.shareLink.value=e.startsWith(`https://`)?`${t}/?url=${encodeURIComponent(e)}`:e)}catch{}if(r){if(Ke=r.live,j=r.live?r.code:``,Ze=n,o.liveControls.hidden=o.adminPanel.hidden||!r.possible,o.goLive.hidden=r.live,o.stopLive.hidden=!r.live,o.sharePhone.hidden=!1,!r.live){o.sharePhone.textContent=r.possible?`Not listed, so nobody can find this in the directory. Go live to list it, with a phone number and a code anyone can call.`:`This machine has no address the world can reach, so it cannot be listed.`;return}if(!n){o.sharePhone.textContent=`Listed. The code for the phone line is ${r.code}.`,o.shareSend.hidden=!1;return}o.sharePhone.replaceChildren(document.createTextNode(`To talk about it, call `),di(n),document.createTextNode(` and key `),di(r.code),document.createTextNode(`. That is a room with everyone else watching — not the stream itself.`)),o.shareSend.hidden=!1}}let Kr=``,qr=null,Jr=e=>{qr&&clearInterval(qr),qr=null,e&&(qr=setInterval(()=>void Q(),6e3))};async function Q(){if(u!==`remote`){o.onairPanel.hidden=!0;return}let e;try{let t=await fetch(L.url(`/api/streams`));if(!t.ok){o.onairPanel.hidden=!0;return}e=await t.json(),m=e.server.carries!==!1,e.server.name&&e.server.name!==d&&(d=e.server.name,o.onairPanel.dataset.title=`Live on ${d}`,o.catalogsPanel.dataset.title=`Catalogs on ${d}`,Tn(),V())}catch{o.onairPanel.hidden=!0;return}o.onairPanel.hidden=!1,O=e,oi(e);let t=`${o.adminPanel.hidden?`view`:`drive`}:${JSON.stringify(e)}`;if(t===Kr)return;Kr=t;let n=e.restreams??[],r=e.channels.length+n.length;o.onairNote.textContent=r===0?`One stream, from this server's own files.`:`${r+1} streams: this server's own files, and ${r} more on it.`;let i=[],a=e.server.playing,s=!o.adminPanel.hidden;i.push(si({title:e.server.name,detail:[a?`playing ${e.server.nowPlaying}`:e.server.nowPlaying?`stopped on ${e.server.nowPlaying}`:`nothing loaded`,`${e.server.tracks} track${e.server.tracks===1?``:`s`}`,e.server.code?`☎ ${e.server.code}`:`not listed`].join(` · `),playLabel:a?`Join live`:s?`Start the stream`:`Nothing playing`,onPlay:()=>{if(a){Zr(e.server.nowPlaying);return}s&&Xr()},link:e.server.live?e.server.url:``,...a?{page:ii(`live`)}:{},direct:a?L.url(`/api/live`):``}));for(let e of n)i.push(si({title:e.name,detail:e.tracks===1?`re-streamed from the web`:`re-streamed from the web · ${e.tracks} tracks`,onPlay:()=>{B(e.at)},link:``,direct:L.media(e.at,0,!0)}));for(let t of e.channels){let e=t.kind!==`audio`,n=L.url(`/api/channels/${encodeURIComponent(t.id)}`),r=[t.via===`pull`?`on the air · ${t.listeners} watching`:`live over ${t.via} · ${t.listeners} listening`];t.code&&r.push(`☎ ${t.code}`),t.redials&&r.push(`redialled ${t.redials}×`),s&&t.error&&r.push(t.error),i.push(si({title:t.name,detail:r.join(` · `),onPlay:()=>{Qr({id:t.id,name:t.name,video:e})},link:n,page:ii(`channel:${t.id}`),direct:n,onRestart:s&&t.via===`pull`?()=>{ei(t.id,t.name)}:void 0,onStop:s||ut&&X!==``&&t.startedBy===X?()=>{ni(t.id,t.name)}:void 0,onRename:s||ut&&X!==``&&t.startedBy===X?()=>{ti(t.id,t.name)}:void 0}))}o.onairList.replaceChildren(...i)}async function Yr(){if(u===`remote`)try{if((await fetch(L.media(R(),0),{method:`GET`,headers:{range:`bytes=0-1`}})).status!==402)return;C=X===``?`This stream is busy enough to be charging for. Sign in to nixamp.com to pay for a pass.`:`This stream is charging for a pass. Follow the payment prompt to keep listening.`,X===``&&o.accountPanel.scrollIntoView({behavior:`smooth`,block:`center`}),V()}catch{}}async function Xr(){W(`Starting the stream on the server…`);try{await L.send({type:`play`,index:Math.max(0,R())})}catch{W(`could not reach the server`);return}await Zr(y.tracks[R()]?.title??``),W(`Playing to the room. Anybody with the view link sees this.`),await Q()}async function Zr(e){w=-1,T=null,D={kind:`live`},Ge(e,`auto`),await E(()=>I.load({title:e||`Live`,artist:``,album:``,duration:0,url:L.url(`/api/live`),video:!0,objectUrl:!1},!0)),U(!0),C=`Watching what this server is playing. Everyone here sees the same thing.`,V()}async function Qr(e,t=!0,n){w=-1,T=e,t&&(xe=0),t&&(D=n??{kind:`channel`}),t&&Ge(e.name,D?.link||tt(e.name)?`auto`:`channel`);let r=e.video&&ft();await E(()=>I.load({title:e.name,artist:``,album:``,duration:0,url:L.url(r?`/api/channels/${encodeURIComponent(e.id)}/hls/index.m3u8`:`/api/channels/${encodeURIComponent(e.id)}`),video:e.video,objectUrl:!1},!0)),U(e.video),T===e&&(C=`Watching ${e.name}, live on this server.`),V()}function $r(){let e=T;return e?Qe?!0:xe>=5?(C=`${e.name} stopped, and did not come back.`,T=null,D=null,V(),!0):(xe+=1,C=`${e.name} started over; rejoining…`,V(),Qe=setTimeout(()=>{Qe=null,T===e&&Qr(e,!1)},2e3),!0):!1}function $(e){o.onairNote.textContent=e,W(e)}async function ei(e,t){$(`Restarting ${t}…`);try{let n=await fetch(L.url(`/api/channels/${encodeURIComponent(e)}/restart`),{method:`POST`}),r=await n.json().catch(()=>({}));$(n.ok?`${t} is dialling its source again.`:r.error??`that did not work`)}catch{$(`could not reach the server`)}Kr=``,Q()}async function ti(e,t){let n=globalThis.prompt(`Call ${t}…`,t);if(n===null)return;let r=n.trim();if(r!==``&&r!==t){$(`Renaming ${t}…`);try{let n=await fetch(L.url(`/api/channels/${encodeURIComponent(e)}`),{method:`PATCH`,headers:{"content-type":`application/json`},body:JSON.stringify({name:r})}),i=await n.json().catch(()=>({}));$(n.ok?`${t} is now ${r}.`:i.error??`that did not work`)}catch{$(`could not reach the server`)}T?.id===e&&(T={...T,name:r}),Kr=``,Q(),V()}}async function ni(e,t){$(`Taking ${t} off the air…`);try{let n=await fetch(L.url(`/api/channels/${encodeURIComponent(e)}`),{method:`DELETE`}),r=await n.json().catch(()=>({}));$(n.ok?`${t} is off the air.`:r.error??`that did not work`)}catch{$(`could not reach the server`)}T?.id===e&&(T=null,I.stop()),Kr=``,Q()}async function ri(e,t,n=`Copied`){if(!e)return;let r=t.innerHTML;try{await navigator.clipboard.writeText(e)}catch{C=e,V();return}n===`✓`||n===`✓`?c(t,`check`):t.textContent=n,setTimeout(()=>{t.innerHTML=r},1200)}function ii(e,t=0){let n=ai();if(n===``)return``;let r=globalThis.location.origin,i=t>1?`&t=${Math.floor(t)}`:``;return`${r}/?url=${encodeURIComponent(n)}&play=${encodeURIComponent(e)}${i}`}function ai(){if(se!==``)return se;let e=u===`remote`?L.shareLink:``;return/\/admin\//.test(e)?``:e}function oi(e){if(g===``)return;let t=g;if(t===`live`){g=``,e.server.playing?Zr(e.server.nowPlaying):(C=`Nothing is playing on this server right now.`,V());return}if(t.startsWith(`link:`)){g=``,Gt(t.slice(5));return}if(t.startsWith(`track:`)){g=``;let e=Number(t.slice(6));Number.isInteger(e)&&e>=0&&B(e);return}let n=t.startsWith(`channel:`)?t.slice(8):``,r=e.channels.find(e=>e.id===n)??e.channels.find(e=>e.name===n);r&&(g=``,Qr({id:r.id,name:r.name,video:r.kind!==`audio`}))}function si(e){let t=document.createElement(`li`);t.className=`onair`;let n=document.createElement(`span`);n.className=`recent-label`;let r=document.createElement(`span`);r.className=`name`,r.textContent=e.title;let i=document.createElement(`span`);i.className=`detail`,i.textContent=e.detail,n.append(r,i);let a=document.createElement(`span`);a.className=`onair-actions`;let o=document.createElement(`button`);o.type=`button`,o.className=`button`,ht(o,e.playLabel??`Join live`),o.addEventListener(`click`,e.onPlay),a.append(o);let s=(e,t,n)=>{let r=document.createElement(`button`);return r.type=`button`,r.className=`icon`,c(r,e),r.title=t,r.setAttribute(`aria-label`,t),r.addEventListener(`click`,()=>n(r)),r};return(e.link||e.page)&&a.append(s(`link`,`Copy a link that opens this in the player`,t=>{let n=globalThis.location.origin;ri(e.page??(e.link.startsWith(`https://`)?`${n}/?url=${encodeURIComponent(e.link)}`:e.link),t,`✓`)})),e.direct&&a.append(s(`copy`,`Copy the stream's own URL, for VLC or mpv`,t=>{ri(e.direct??``,t,`✓`)})),e.onRestart&&a.append(s(`restart`,`Restart: dial the source again`,()=>e.onRestart?.())),e.onRename&&a.append(s(`rename`,`Rename: call it something better in the directory`,()=>e.onRename?.())),e.onStop&&a.append(s(`remove`,`Remove: take it off the air`,()=>e.onStop?.())),t.append(n,a),t}let ci=``;function li(e,t){ci=e;let n=e!==``&&t===e;o.loadHome.hidden=e===``,o.homeNote.hidden=e===``,e!==``&&(o.homeNote.textContent=n?`This server's own files: ${e}`:`This server's own files are ${e}, and are not in the playlist.`,o.loadHome.disabled=!1)}o.loadHome.addEventListener(`click`,()=>{ci!==``&&(o.loadHome.disabled=!0,W(`Reading this server's files…`),(async()=>{try{let e=await fetch(L.url(`/api/source`),{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({source:ci})}),t=await e.json();W(e.ok?t.added===0?`This server's files are already in the playlist.`:`Loaded ${t.added??0} of this server's own files.`:t.error??`that did not work`)}catch{W(`could not reach the server`)}finally{o.loadHome.disabled=!1}})())});let ui=async e=>{o.goLive.disabled=!0,o.stopLive.disabled=!0,o.shareNote.textContent=e?`Going live…`:`Taking it off the list…`;try{let t=await fetch(L.url(e?`/api/live/start`:`/api/live/stop`),{method:`POST`}),n=await t.json();o.shareNote.textContent=t.ok?e?`Live. Anyone can call and key ${n.code??``} to talk about it.`:`Taken off the list. The link still works for anybody who has it.`:n.error??`that did not work`}catch{o.shareNote.textContent=`could not reach the server`}finally{o.goLive.disabled=!1,o.stopLive.disabled=!1,await Gr()}};o.goLive.addEventListener(`click`,()=>void ui(!0)),o.stopLive.addEventListener(`click`,()=>void ui(!1));function di(e){let t=document.createElement(`b`);return t.textContent=e,t}o.shareCopy.addEventListener(`click`,()=>{o.shareLink.select(),navigator.clipboard?.writeText(o.shareLink.value).then(()=>{o.shareNote.textContent=`Copied. Send it to anybody.`},()=>{o.shareNote.textContent=`Copy it from the box above.`})}),o.shareSend.addEventListener(`submit`,e=>{e.preventDefault();let t=o.shareTo.value.trim();t!==``&&(async()=>{o.shareNote.textContent=`Sending…`;try{let e=await fetch(`/api/v1/invite`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({to:t,stream:ai()})}),n=await e.json();o.shareNote.textContent=e.ok?`Sent to ${n.sent??t}.`:n.error??`that did not send`,e.ok&&(o.shareTo.value=``)}catch{o.shareNote.textContent=`could not send that`}})()}),o.listenHere.addEventListener(`change`,()=>{try{localStorage.setItem(it,o.listenHere.checked?`1`:`0`)}catch{}u===`remote`&&(async()=>{o.listenHere.checked?(await L.send({type:`stop`}),await Ct(y.index)):(I.stop(),w=-1),V()})()}),document.addEventListener(`keydown`,e=>{let t=e.target;if(!(t&&/^(INPUT|TEXTAREA|SELECT)$/.test(t.tagName)))switch(e.key){case` `:e.preventDefault(),wt();return;case`s`:Et();return;case`n`:case`ArrowRight`:Tt(1);return;case`p`:case`ArrowLeft`:Tt(-1);return;case`ArrowDown`:e.preventDefault(),B(Math.min(_t()-1,R()+1));return;case`ArrowUp`:e.preventDefault(),B(Math.max(0,R()-1));return}});let fi=null;globalThis.addEventListener(`beforeinstallprompt`,e=>{e.preventDefault(),fi=e,o.install.hidden=!1}),o.install.addEventListener(`click`,()=>{fi?.prompt(),fi=null,o.install.hidden=!0});try{let e=localStorage.getItem(rt);e!==null&&(o.volume.value=String(Math.round(Number(e)*100)),I.volume=Number(e));let t=localStorage.getItem(nt);t&&(o.remoteUrl.value=t),localStorage.getItem(it)===`0`&&(o.listenHere.checked=!1)}catch{}(async()=>{if(o.remoteUrl.value!==``)return;let e=globalThis.location.origin;if(await ye(e)===null)return;let t=await ge(e);t&&t.trackCount!==0&&(o.remoteUrl.value=e,u=`remote`,C=``,L.connect(e),V())})(),(()=>{if(ot||$e!==``)return;let e=()=>I.source!==``||I.playing||Ae()||T!==null,t=async()=>{try{let e=await(await fetch(`/jingles/index.json`)).json();if(Array.isArray(e)&&e.length>0){let t=e[Math.floor(Math.random()*e.length)];if(typeof t==`string`)return`/jingles/${t}`}}catch{}return``},n=new Audio;n.volume=.7;let r=()=>{ot=!0},i=()=>{document.removeEventListener(`pointerdown`,i),document.removeEventListener(`keydown`,i),r(),setTimeout(()=>{e()||n.play().catch(()=>{})},150)};t().then(t=>{if(!(t===``||e()))return n.src=t,n.play().then(r,()=>{document.addEventListener(`pointerdown`,i,{once:!0}),document.addEventListener(`keydown`,i,{once:!0})})})})(),V(),requestAnimationFrame(Vt)}st(),`serviceWorker`in navigator&&globalThis.addEventListener(`load`,()=>{navigator.serviceWorker.register(`/sw.js`).catch(()=>{})});export{c as t};
|