nixamp 0.2.0 → 0.4.0

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.
Files changed (69) hide show
  1. package/README.md +171 -0
  2. package/dist/accounts.d.ts +54 -0
  3. package/dist/accounts.js +160 -0
  4. package/dist/broadcast.d.ts +96 -0
  5. package/dist/broadcast.js +193 -0
  6. package/dist/channels.d.ts +94 -0
  7. package/dist/channels.js +235 -0
  8. package/dist/connections.d.ts +6 -0
  9. package/dist/connections.js +13 -0
  10. package/dist/directory.d.ts +186 -0
  11. package/dist/directory.js +275 -0
  12. package/dist/durable.d.ts +70 -0
  13. package/dist/durable.js +156 -0
  14. package/dist/follows.d.ts +92 -0
  15. package/dist/follows.js +248 -0
  16. package/dist/ingest.d.ts +80 -0
  17. package/dist/ingest.js +252 -0
  18. package/dist/main.js +21 -0
  19. package/dist/manage.js +2 -1
  20. package/dist/notify.d.ts +83 -0
  21. package/dist/notify.js +126 -0
  22. package/dist/optin.d.ts +37 -0
  23. package/dist/optin.js +122 -0
  24. package/dist/owner.d.ts +53 -0
  25. package/dist/owner.js +96 -0
  26. package/dist/partyline.d.ts +259 -0
  27. package/dist/partyline.js +616 -0
  28. package/dist/paywall.d.ts +60 -0
  29. package/dist/paywall.js +162 -0
  30. package/dist/playlist.js +5 -0
  31. package/dist/publish.d.ts +57 -0
  32. package/dist/publish.js +106 -0
  33. package/dist/rtmp-in.d.ts +22 -0
  34. package/dist/rtmp-in.js +79 -0
  35. package/dist/server.d.ts +94 -0
  36. package/dist/server.js +1158 -12
  37. package/dist/session.d.ts +29 -0
  38. package/dist/session.js +184 -0
  39. package/dist/share.d.ts +26 -0
  40. package/dist/share.js +31 -0
  41. package/package.json +8 -2
  42. package/src/accounts.ts +193 -0
  43. package/src/broadcast.ts +264 -0
  44. package/src/channels.ts +281 -0
  45. package/src/connections.ts +13 -0
  46. package/src/directory.ts +362 -0
  47. package/src/durable.ts +215 -0
  48. package/src/follows.ts +307 -0
  49. package/src/ingest.ts +297 -0
  50. package/src/main.ts +21 -0
  51. package/src/manage.ts +2 -1
  52. package/src/notify.ts +217 -0
  53. package/src/optin.ts +128 -0
  54. package/src/owner.ts +113 -0
  55. package/src/partyline.ts +742 -0
  56. package/src/paywall.ts +198 -0
  57. package/src/playlist.ts +5 -0
  58. package/src/publish.ts +137 -0
  59. package/src/rtmp-in.ts +90 -0
  60. package/src/server.ts +1304 -12
  61. package/src/session.ts +209 -0
  62. package/src/share.ts +40 -0
  63. package/src/types/auth-system.d.ts +77 -0
  64. package/web/dist/assets/{index-BGKWWaIx.css → index-DSIDSSPF.css} +1 -1
  65. package/web/dist/assets/index-qRguFskX.js +1 -0
  66. package/web/dist/index.html +62 -6
  67. package/web/dist/install.sh +82 -0
  68. package/web/dist/sw.js +45 -3
  69. package/web/dist/assets/index-Dhja5wxB.js +0 -1
package/web/dist/sw.js CHANGED
@@ -1,10 +1,10 @@
1
1
  /* nixamp service worker — generated, do not edit */
2
- const CACHE = "nixamp-1788894378334";
2
+ const CACHE = "nixamp-1788930201175";
3
3
  const PRECACHE = [
4
4
  "/",
5
5
  "/apple-touch-icon.png",
6
- "/assets/index-BGKWWaIx.css",
7
- "/assets/index-Dhja5wxB.js",
6
+ "/assets/index-DSIDSSPF.css",
7
+ "/assets/index-qRguFskX.js",
8
8
  "/icons/icon-192-maskable.png",
9
9
  "/icons/icon-192.png",
10
10
  "/icons/icon-512-maskable.png",
@@ -35,6 +35,48 @@ self.addEventListener("message", (event) => {
35
35
  if (event.data === "skip-waiting") self.skipWaiting();
36
36
  });
37
37
 
38
+ // A broadcaster somebody follows went live. This is the same code path for a
39
+ // browser tab, an installed PWA and the desktop app: all three are a push
40
+ // subscription, and none of them has to be open to receive one.
41
+ self.addEventListener("push", (event) => {
42
+ // A push with no payload is still a push. Showing something beats showing
43
+ // nothing, and some services strip the body.
44
+ let note = { title: "nixamp", body: "Someone you follow is live.", url: "/directory" };
45
+ try {
46
+ if (event.data) note = { ...note, ...event.data.json() };
47
+ } catch (_) {}
48
+
49
+ event.waitUntil(
50
+ self.registration.showNotification(note.title, {
51
+ body: note.body,
52
+ icon: "/icons/icon-192.png",
53
+ badge: "/icons/icon-192.png",
54
+ // Tagged by destination so a second notification about the same stream
55
+ // replaces the first rather than stacking up behind it.
56
+ tag: "nixamp-live-" + (note.url || ""),
57
+ renotify: false,
58
+ data: { url: note.url },
59
+ }),
60
+ );
61
+ });
62
+
63
+ self.addEventListener("notificationclick", (event) => {
64
+ event.notification.close();
65
+ const target = (event.notification.data && event.notification.data.url) || "/directory";
66
+ event.waitUntil((async () => {
67
+ // Focus a window we already have rather than opening a fourth copy of the
68
+ // app, which is what happens if you just call openWindow every time.
69
+ const windows = await self.clients.matchAll({ type: "window", includeUncontrolled: true });
70
+ for (const client of windows) {
71
+ if ("focus" in client) {
72
+ if ("navigate" in client) await client.navigate(target).catch(() => {});
73
+ return client.focus();
74
+ }
75
+ }
76
+ return self.clients.openWindow(target);
77
+ })());
78
+ });
79
+
38
80
  self.addEventListener("fetch", (event) => {
39
81
  const request = event.request;
40
82
  if (request.method !== "GET") return;
@@ -1 +0,0 @@
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`]);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=new Set([`mp3`,`flac`,`ogg`,`oga`,`opus`,`m4a`,`aac`,`wav`,`wma`,`aiff`,`aif`,`alac`,`mp4`,`webm`,`mkv`,`mov`,`m4v`,`ogv`]);function o(e,t=``){if(t.startsWith(`audio/`)||t.startsWith(`video/`))return!0;let n=e.lastIndexOf(`.`);return n>0&&a.has(e.slice(n+1).toLowerCase())}function s(e,t){return e.localeCompare(t,void 0,{numeric:!0,sensitivity:`base`})}function c(e){return e.filter(e=>o(e.name,e.type)).sort((e,t)=>s(l(e),l(t))).map(e=>({title:n(e.name),artist:``,album:u(l(e)),duration:0,url:URL.createObjectURL(e),video:i(e.name,e.type),objectUrl:!0}))}function l(e){return e.webkitRelativePath||e.name}function u(e){let t=e.split(`/`);return t.length>1?t[t.length-2]:``}function d(e){for(let t of e)t.objectUrl&&URL.revokeObjectURL(t.url)}var f=2048,p=class{elements;handlers;context=null;analyser=null;wired=new WeakSet;active;frequencies=new Uint8Array;constructor(e,t){this.elements=e,this.handlers=t,this.active=e.audio;for(let t of[e.audio,e.video])t.crossOrigin=`anonymous`,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(m(t))})}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=f,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){let n=e.video?this.elements.video:this.elements.audio;n!==this.active&&(this.active.pause(),this.active.removeAttribute(`src`),this.active.load(),this.active=n),this.active.src=e.url,this.active.load(),t&&await this.play()}async play(){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}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 m(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 h(){return{revision:0,tracks:[],index:0,playing:!1,position:0,bars:[],levels:[0,0],silent:!0,note:``,root:``}}function g(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 _(e,t){return`${e===``?``:g(e)}${t.startsWith(`/`)?t:`/${t}`}`}function v(e,t){return _(e,`/api/media/${t}`)}function y(e){if(typeof e!=`object`||!e)return null;let t=e;if(!Array.isArray(t.tracks))return null;let n=h(),r=(e,t)=>typeof e==`number`&&Number.isFinite(e)?e:t,i=Array.isArray(t.levels)?t.levels:[];return{revision:r(t.revision,0),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)}}),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 b=class{handlers;source=null;base=``;lastRevision=-1;constructor(e){this.handlers=e}get address(){return this.base}get connected(){return this.source!==null}connect(e){let t=g(e);this.close(),this.base=t,this.lastRevision=-1,this.handlers.onStatus(`connecting`);let n=new EventSource(_(t,`/api/events`));this.source=n,n.onopen=()=>this.handlers.onStatus(`live`),n.onmessage=e=>{let t=y(x(e.data));t&&(t.revision<this.lastRevision||(this.lastRevision=t.revision,this.handlers.onStatus(`live`),this.handlers.onSnapshot(t)))},n.onerror=()=>{this.handlers.onStatus(`error`,`reconnecting…`)}}async send(e){if(this.base===``&&!this.connected)return;let t=await fetch(_(this.base,`/api/command`),{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=y(await t.json());n&&this.handlers.onSnapshot(n)}media(e){return v(this.base,e)}close(){this.source?.close(),this.source=null}};function x(e){try{return JSON.parse(e)}catch{return null}}async function S(e,t){try{let n=await fetch(_(e,`/api/state`),{signal:t});return n.ok?y(await n.json()):null}catch{return null}}async function C(e,t){try{let n=await fetch(_(e,`/api/health`),{signal:t});if(!n.ok)return null;let r=await n.json();return r.name===`nixamp`?r.version??`unknown`:null}catch{return null}}var w=.14,T=.02;function E(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 D(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 O(e,t,n=w){return t.map((t,r)=>{let i=e[r]??0;return t>=i?t:Math.max(t,i-n)})}function k(e,t,n=T){return t.map((t,r)=>Math.max(t,(e[r]??0)-n))}function A(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)})}var j=`nixamp.remote`,M=`nixamp.volume`;function N(e){let t=document.getElementById(e);if(!t)throw Error(`nixamp: #${e} is missing from the shell`);return t}function P(){let n={status:N(`status`),source:N(`source`),install:N(`install`),video:N(`video`),audio:N(`audio`),title:N(`title-line`),album:N(`album-line`),elapsed:N(`elapsed`),total:N(`total`),seek:N(`seek`),canvas:N(`spectrum`),glyphs:N(`glyphs`),levels:N(`levels`),playlist:N(`playlist`),playlistTitle:N(`playlist-panel`),note:N(`note`),files:N(`files`),folder:N(`folder`),remoteUrl:N(`remote-url`),remoteForm:N(`remote-form`),remoteState:N(`remote-state`),disconnect:N(`disconnect`),listenHere:N(`listen-here`),volume:N(`volume`),prev:N(`prev`),playPause:N(`play-pause`),stop:N(`stop`),next:N(`next`)},r=`local`,i=[],a=0,o=h(),s=`idle`,l=``,u=`Pick files, or connect to a nixamp running somewhere else.`,f=!1,m=Array(24).fill(0),_=Array(24).fill(0),v=[],y=()=>r===`remote`&&!n.listenHere.checked,x=new p({audio:n.audio,video:n.video},{onTime:(e,t)=>{let n=i[a];r===`local`&&n&&t>0&&n.duration!==t&&(n.duration=t),K()},onEnded:()=>U(1),onState:()=>K(),onError:e=>{u=e,K()}}),w=new b({onSnapshot:e=>{o=e,y()&&(m=e.bars.length>0?e.bars:m,_=k(_,m)),K()},onStatus:(e,t)=>{s=e,l=t??``,K()}}),T=()=>r===`remote`?o.tracks.length:i.length,P=()=>r===`remote`?o.index:a,F=()=>{if(r===`remote`){let e=o.tracks[o.index];return e?t(e):`Nothing loaded.`}let e=i[a];return e?t(e):`Nothing loaded.`},I=()=>(r===`remote`?o.tracks[o.index]:i[a])?.album||`—`,L=()=>y()?o.tracks[o.index]?.duration??0:x.duration,R=()=>y()?o.position:x.position,z=()=>y()?o.playing:x.playing;async function B(e){if(r===`remote`){if(y()){await w.send({type:`play`,index:e});return}await w.send({type:`select`,index:e}),await V(e);return}let t=i[e];t&&(a=e,await x.load(t,!0),X(t.video),Z(),K())}async function V(e){let t=o.tracks[e];t&&(await x.load({title:t.title,artist:t.artist,album:t.album,duration:t.duration,url:w.media(e),video:!1,objectUrl:!1},!0),Z())}async function H(){if(y()){await w.send({type:`toggle`});return}T()!==0&&(x.playing?x.pause():x.position>0?await x.play():await B(P()),K())}async function U(e){let t=T();if(t!==0){if(y()){await w.send({type:e>0?`next`:`prev`});return}await B((P()+e+t)%t)}}async function W(){if(y()){await w.send({type:`stop`});return}x.stop(),m=Array(24).fill(0),_=[...m],K()}let G=e=>`▁▂▃▄▅▆▇█`[Math.max(0,Math.min(7,Math.round(e*7)))];function K(){let t=T(),a=z();n.status.textContent=a?`▶ PLAYING`:`■ STOPPED`,n.status.dataset.playing=String(a),n.title.textContent=F(),n.album.textContent=I();let c=R(),d=L();n.elapsed.textContent=e(c),n.total.textContent=d>0?e(d):`--:--`,f||(n.seek.value=String(d>0?Math.round(c/d*1e3):0),n.seek.disabled=d<=0||y()),n.playPause.textContent=a?`❚❚`:`▶`,n.playPause.setAttribute(`aria-label`,a?`Pause`:`Play`),n.playlistTitle.dataset.title=`Playlist (${t})`,n.source.textContent=r===`remote`?`remote · ${w.address.replace(/^https?:\/\//,``)||`—`}`:i.length>0?`local · ${i.length} files`:`no source`,n.remoteState.textContent=r===`remote`?`${s}${l?` — ${l}`:``}`:`not connected`,n.remoteState.dataset.status=r===`remote`?s:`idle`,n.disconnect.hidden=r!==`remote`;let p=r===`remote`&&o.note!==``?o.note:u;n.note.textContent=p,n.note.hidden=p===``,J(),n.glyphs.textContent=m.map(G).join(``);let[h,g]=y()?o.levels:x.levels();n.levels.textContent=`L${`▮`.repeat(Math.round(h*6)).padEnd(6,`·`)} R${`▮`.repeat(Math.round(g*6)).padEnd(6,`·`)}`}let q=``;function J(){let a=r===`remote`?o.tracks.map(e=>[t(e),e.duration]):i.map(e=>[t(e),e.duration]),s=`${r}:${a.map(([e,t])=>`${e}@${t}`).join(`|`)}`;s!==q&&(q=s,n.playlist.replaceChildren(...a.map(([t,n],r)=>{let i=document.createElement(`li`);i.className=`row`,i.dataset.index=String(r);let a=document.createElement(`span`);a.className=`n`,a.textContent=String(r+1).padStart(2,` `);let o=document.createElement(`span`);o.className=`name`,o.textContent=t;let s=document.createElement(`span`);return s.className=`time`,s.textContent=n>0?e(n):`--:--`,i.append(a,o,s),i})));let c=P(),l=z();Array.from(n.playlist.children).forEach((e,t)=>{let n=e;n.classList.toggle(`selected`,t===c),n.classList.toggle(`playing`,t===c&&l)}),n.playlist.children[c]?.scrollIntoView({block:`nearest`})}function Y(){let t=n.canvas,r=Math.min(2,globalThis.devicePixelRatio||1),i=Math.round(t.clientWidth*r),a=Math.round(t.clientHeight*r);i>0&&a>0&&(t.width!==i||t.height!==a)&&(t.width=i,t.height=a);let s=t.getContext(`2d`);if(y())_=k(_,m);else{let e=x.read();e.length>0&&(v.length!==25&&(v=E(24,e.length)),m=O(m,D(e,v)),_=k(_,m))}if(s){let e=getComputedStyle(document.documentElement);A(s,{width:t.width,height:t.height},m,_,{bar:e.getPropertyValue(`--green`).trim()||`#4af689`,peak:e.getPropertyValue(`--green-dim`).trim()||`#227a4a`,background:`transparent`})}if(z()){n.glyphs.textContent=m.map(G).join(``);let[t,r]=y()?o.levels:x.levels();n.levels.textContent=`L${`▮`.repeat(Math.round(t*6)).padEnd(6,`·`)} R${`▮`.repeat(Math.round(r*6)).padEnd(6,`·`)}`,n.elapsed.textContent=e(R());let i=L();!f&&i>0&&(n.seek.value=String(Math.round(R()/i*1e3)))}requestAnimationFrame(Y)}function X(e){n.video.hidden=!e}function Z(){`mediaSession`in navigator&&(navigator.mediaSession.metadata=new MediaMetadata({title:F(),album:I(),artist:`nixamp`,artwork:[{src:`/icons/icon-512.png`,sizes:`512x512`,type:`image/png`}]}),navigator.mediaSession.setActionHandler(`play`,()=>void H()),navigator.mediaSession.setActionHandler(`pause`,()=>void H()),navigator.mediaSession.setActionHandler(`nexttrack`,()=>void U(1)),navigator.mediaSession.setActionHandler(`previoustrack`,()=>void U(-1)))}n.playlist.addEventListener(`click`,e=>{let t=e.target.closest(`li`),n=Number(t?.dataset.index);Number.isInteger(n)&&B(n)}),n.prev.addEventListener(`click`,()=>void U(-1)),n.next.addEventListener(`click`,()=>void U(1)),n.stop.addEventListener(`click`,()=>void W()),n.playPause.addEventListener(`click`,()=>void H()),n.seek.addEventListener(`input`,()=>{f=!0}),n.seek.addEventListener(`change`,()=>{let e=L();e>0&&x.seek(Number(n.seek.value)/1e3*e),f=!1}),n.volume.addEventListener(`input`,()=>{let e=Number(n.volume.value)/100;x.volume=e;try{localStorage.setItem(M,String(e))}catch{}});let Q=e=>{e.addEventListener(`change`,()=>{let t=c(Array.from(e.files??[]));if(t.length===0){u=`Nothing playable in that selection.`,K();return}d(i),i=t,a=0,r=`local`,w.close(),u=``,B(0)})};Q(n.files),Q(n.folder),n.remoteForm.addEventListener(`submit`,e=>{e.preventDefault();let t=g(n.remoteUrl.value);if(t===``){u=`That is not an address.`,K();return}(async()=>{if(s=`connecting`,K(),await C(t)===null){s=`error`,l=`no nixamp answered there`,r=`local`,K();return}r=`remote`,u=``;try{localStorage.setItem(j,t)}catch{}w.connect(t),K()})()}),n.disconnect.addEventListener(`click`,()=>{w.close(),r=`local`,s=`idle`,l=``,K()}),n.listenHere.addEventListener(`change`,()=>{r===`remote`&&(async()=>{n.listenHere.checked?(await w.send({type:`stop`}),await V(o.index)):x.stop(),K()})()}),document.addEventListener(`keydown`,e=>{let t=e.target;if(!(t&&/^(INPUT|TEXTAREA|SELECT)$/.test(t.tagName)))switch(e.key){case` `:e.preventDefault(),H();return;case`s`:W();return;case`n`:case`ArrowRight`:U(1);return;case`p`:case`ArrowLeft`:U(-1);return;case`ArrowDown`:e.preventDefault(),B(Math.min(T()-1,P()+1));return;case`ArrowUp`:e.preventDefault(),B(Math.max(0,P()-1));return}});let $=null;globalThis.addEventListener(`beforeinstallprompt`,e=>{e.preventDefault(),$=e,n.install.hidden=!1}),n.install.addEventListener(`click`,()=>{$?.prompt(),$=null,n.install.hidden=!0});try{let e=localStorage.getItem(M);e!==null&&(n.volume.value=String(Math.round(Number(e)*100)),x.volume=Number(e));let t=localStorage.getItem(j);t&&(n.remoteUrl.value=t)}catch{}(async()=>{if(n.remoteUrl.value!==``)return;let e=globalThis.location.origin;if(await C(e)===null)return;let t=await S(e);t&&t.tracks.length!==0&&(n.remoteUrl.value=e,r=`remote`,u=``,w.connect(e),K())})(),K(),requestAnimationFrame(Y)}P(),`serviceWorker`in navigator&&globalThis.addEventListener(`load`,()=>{navigator.serviceWorker.register(`/sw.js`).catch(()=>{})});