UNPKG

jukette

Version:

White-label jukebox custom element for audio, SoundCloud, and MIDI playlists.

71 lines (65 loc) 36.9 kB
/*! jukette v0.9.0 | (c) 2026 Rémino Rem <https://remino.net/> | ISC Licence */ (function(){var e=new Map,t=new Set,n=()=>Array.from(e.values()),r=t=>e.get(t),i=n=>{e.set(n.type,n);for(let e of t)e(n);return n},a=e=>(t.add(e),()=>{t.delete(e)}),o=e=>{if(e.type)return r(e.type);for(let t of n().sort((e,t)=>(t.priority??0)-(e.priority??0)))if(t.inferTrackType?.(e)===t.type)return t},s=globalThis.HTMLElement??class{},c=`playlist`,l=`playlist-src`,ee=`preload`,u=`preload-metadata`,d=`prefer-media-metadata`,f=`display-marquee`,p=`midi-oscillator`,m=`track-index`,te=`title`,ne=`artist`,re=`type`,h=`show-source-link`,g=`show-track-select`,ie=e=>typeof e==`object`&&!!e,_=e=>{if(e===null)return;let t=e.trim().toLowerCase();if(t===``||t===`true`)return!0;if(t===`false`)return!1},ae=e=>{if(typeof e==`number`)return Number.isFinite(e)?Math.max(0,e):void 0;if(typeof e!=`string`)return;let t=e.trim();if(!t)return;let n=Number(t);return Number.isFinite(n)?Math.max(0,n):void 0},v=e=>{if(e.type)return e.type;for(let t of n().sort((e,t)=>(t.priority??0)-(e.priority??0))){let n=t.inferTrackType?.(e);if(n)return n}let t=e.src.toLowerCase();return/\.(?:mid|midi)(?:[?#].*)?$/.test(t)?`midi`:`audio`},y=e=>{if(typeof e==`string`){let t=e.trim();return t?{src:t}:null}if(!ie(e)||typeof e.src!=`string`)return null;let t=e.src.trim();if(!t)return null;let n={src:t};if(typeof e.artist==`string`&&(n.artist=e.artist),typeof e.preferMediaMetadata==`boolean`)n.preferMediaMetadata=e.preferMediaMetadata;else if(typeof e.preferMediaMetadata==`string`){let t=_(e.preferMediaMetadata);t!==void 0&&(n.preferMediaMetadata=t)}if(typeof e.preload==`boolean`)n.preload=e.preload;else if(typeof e.preload==`string`){let t=_(e.preload);t!==void 0&&(n.preload=t)}if(typeof e.showSourceLink==`boolean`)n.showSourceLink=e.showSourceLink;else if(typeof e.showSourceLink==`string`){let t=_(e.showSourceLink);t!==void 0&&(n.showSourceLink=t)}let r=ae(e.startAt);return r!==void 0&&(n.startAt=r),typeof e.title==`string`&&(n.title=e.title),typeof e.type==`string`&&e.type.trim()&&(n.type=e.type.trim()),n},oe=e=>{if(!e)return[];try{return b(JSON.parse(e))}catch{return e.split(` `).map(e=>y(e)).filter(e=>e!==null)}},b=e=>(Array.isArray(e)?e:[e]).map(e=>y(e)).filter(e=>e!==null),se=e=>e.localName===`jukette-track`?y({artist:e.getAttribute(`artist`)??void 0,preferMediaMetadata:e.getAttribute(`prefer-media-metadata`)??void 0,preload:e.getAttribute(`preload`)??void 0,showSourceLink:e.getAttribute(`show-source-link`)??void 0,startAt:e.getAttribute(`start-at`)??void 0,src:e.getAttribute(`src`)??``,title:e.getAttribute(`title`)??void 0,type:e.getAttribute(`type`)??void 0}):null,ce=e=>({...e,tracks:[...e.tracks],type:e.track?v(e.track):void 0}),le=e=>e===`sine`||e===`square`||e===`sawtooth`||e===`triangle`?e:`auto`,x=e=>e===`always`||e===`never`||e===`overflow`?e:`overflow`,S=e=>{let t=e.title.trim(),n=e.artist.trim();return n?`${t} - ${n}`:t},ue=`:host{--jukette-control-size:2em;font:inherit;color:inherit;min-width:4em;display:block;overflow:hidden;container-type:inline-size}*{box-sizing:border-box}.player{border:1px solid;gap:.5lh;padding:.5rlh 1em;display:grid}.track{grid-template-columns:minmax(0,1fr) auto;align-items:start;min-inline-size:0;display:grid}.display{white-space:nowrap;align-self:center;font-weight:700;display:block;overflow:hidden}.source-link{aspect-ratio:1;color:inherit;max-block-size:var(--jukette-control-size);align-self:stretch;place-items:center;margin-inline-start:.5em;font-weight:700;line-height:1;text-decoration:none;display:inline-grid}.source-link-glyph{transform:translateY(-.06em)}.source-link:focus-visible{outline-offset:0;outline:2px solid}.source-link[hidden]{display:none}.controls{grid-template-columns:var(--jukette-control-size) minmax(0, 1fr) auto;align-items:center;gap:.5em;display:grid}.play{block-size:var(--jukette-control-size);inline-size:var(--jukette-control-size)}button{appearance:none;block-size:var(--jukette-control-size);color:inherit;cursor:pointer;font:inherit;inline-size:var(--jukette-control-size);background:0 0;border:1px solid;justify-content:center;align-items:center;padding:0;display:inline-grid}button:focus-visible{outline-offset:0;outline-radius:0;outline:2px solid}button:active,button[aria-pressed=true]{background:rgb(from currentColor calc(255 - r) calc(255 - g) calc(255 - b));color:rgb(from currentColor calc(255 - r) calc(255 - g) calc(255 - b))}button:disabled{cursor:default;opacity:.45}input[type=range]{accent-color:currentColor;width:100%;margin:0}.seek{display:grid}.time{text-align:end;white-space:nowrap;border:0;justify-content:end;justify-items:end;block-size:auto;inline-size:auto;padding:0}.time time{font-variant-numeric:tabular-nums;min-inline-size:3em;font-family:monospace;display:block}.track-picker{display:grid}.track-picker[hidden]{display:none}.track-select{appearance:none;color:inherit;cursor:pointer;font:inherit;inline-size:100%;text-overflow:ellipsis;background:0 0;border:1px solid;border-radius:0;max-width:100%;padding:.35rem .5em;overflow:hidden}.track-select:focus-visible{outline-offset:0;outline:2px solid}audio{display:none}@container (width<=11rem){.controls{grid-template-columns:var(--jukette-control-size) minmax(0, 1fr) auto}.seek{visibility:hidden}}`,C=(e,t)=>{let n=e.querySelector(t);if(!n)throw Error(`Missing Jukette element: ${t}`);return n},de=e=>{let t=e.attachShadow({mode:`open`});return t.innerHTML=` <style>${ue}</style> <div class="player" part="player"> <div class="track" part="track" aria-live="polite"> <re-marquee class="display" part="display" role="status" aria-live="polite" animate="overflow"></re-marquee> <a class="source-link" part="source-link" aria-label="Open source page" hidden rel="noopener noreferrer" target="_blank" title="Open source page" ><span class="source-link-glyph">↗</span></a> </div> <div class="controls" part="controls"> <button class="play" part="button play-button" type="button" aria-label="Play">▶</button> <div class="seek" part="seek"> <input class="seek-input" part="seek-input" type="range" min="0" max="1000" value="0" aria-label="Seek" /> </div> <button class="time" part="time" type="button" aria-label="Toggle time display"><time datetime="PT0S">0:00</time></button> </div> <div class="track-picker" part="track-picker"> <select class="track-select" part="track-select" aria-label="Track selection"></select> </div> <audio preload="metadata"></audio> </div> `,{audio:C(t,`audio`),displayElement:C(t,`.display`),playButton:C(t,`.play`),playerElement:C(t,`.player`),seekInput:C(t,`.seek-input`),sourceLink:C(t,`.source-link`),trackPicker:C(t,`.track-picker`),timeButton:C(t,`.time`),timeElement:C(t,`.time time`),trackSelect:C(t,`.track-select`)}},fe=class{options;durations=new Map;metadata=new Map;preloadId=0;constructor(e){this.options=e}get metadataPreloadId(){return this.preloadId}getDuration(e){if(e)return this.durations.get(this.options.getTrackKey(e))}setDuration(e,t){if(!e||!Number.isFinite(t)||t<=0)return;let n=this.options.getTrackKey(e),r=this.durations.get(n);r!==void 0&&Math.abs(r-t)<.5||(this.durations.set(n,t),this.options.onPlaylistDisplayChange())}getDisplay(e){let t=this.metadata.get(this.options.getTrackKey(e)),n=this.options.trackPrefersMediaMetadata(e);return{artist:n?t?.artist||e.artist||``:e.artist||t?.artist||``,title:n?t?.title||e.title||e.src:e.title||t?.title||e.src}}setMetadata(e,t){if(!e)return;let n={artist:t.artist?.trim()||void 0,title:t.title?.trim()||void 0};if(!n.artist&&!n.title)return;let r=this.options.getTrackKey(e),i=this.metadata.get(r);i!==void 0&&i.artist===n.artist&&i.title===n.title||(this.metadata.set(r,n),this.options.isCurrentTrack(e)&&this.options.onCurrentTrackDisplayChange(),this.options.onPlaylistDisplayChange())}preloadPlaylistMetadata(){this.preloadId+=1;let e=this.options.getTracks(),t=e.some(e=>this.options.trackPrefersMediaMetadata(e)),n=e.some(e=>e.preload);if(!this.options.getPreloadMetadata()&&!t&&!n)return;let r=this.preloadId;for(let t of e)this.preloadTrackMetadata(t,r)}async preloadTrackMetadata(e,t){let n=o(e);if(n?.preloadTrack)try{let r=await n.preloadTrack(e,{host:this.options.getHost(),preloadDuration:this.options.getPreloadMetadata(),preloadMetadata:this.options.trackPrefersMediaMetadata(e),prepare:!!e.preload,trackElement:this.options.getTrackElement(e)});if(t!==this.preloadId||!r)return;r.duration&&this.setDuration(e,r.duration),r.metadata&&this.setMetadata(e,r.metadata)}catch{}}},pe=e=>String(e).padStart(2,`0`),w=e=>{let t=Math.floor(Number.isFinite(e)?Math.max(0,e):0);return`${Math.floor(t/60)}:${pe(t%60)}`},me=class{options;progressFrame=0;constructor(e){this.options=e}setStatus(e=``){this.options.onStatusChange(e)}syncProgress(e,t){let n=Number.isFinite(t)?Math.max(0,t):0,r=Number.isFinite(e)?Math.min(Math.max(0,e),n||2**53-1):0,i=n>0?Math.min(1,Math.max(0,r/n)):0;this.options.dom.seekInput.value=String(Math.round(i*1e3));let a=this.options.getTimeMode()===`remaining`?`-${w(Math.max(0,n-r))}`:w(r);this.options.dom.timeElement.textContent=a,this.options.dom.timeElement.dateTime=`PT${Math.max(0,Math.round(this.options.getTimeMode()===`remaining`?n-r:r))}S`}syncPlayingState(){let e=this.options.getPlaying();this.options.dom.playButton.textContent=e?`Ⅱ`:`▶`,this.options.dom.playButton.setAttribute(`aria-label`,e?`Pause`:`Play`),e?(this.setStatus(),this.start()):this.stop()}start(){if(this.progressFrame||typeof requestAnimationFrame>`u`)return;let e=()=>{if(!this.options.getPlaying()){this.progressFrame=0;return}this.syncProgress(this.options.getCurrentTime(),this.options.getDuration()),this.progressFrame=requestAnimationFrame(e)};this.progressFrame=requestAnimationFrame(e)}stop(){if(!this.progressFrame||typeof cancelAnimationFrame>`u`){this.progressFrame=0;return}cancelAnimationFrame(this.progressFrame),this.progressFrame=0,this.syncProgress(this.options.getCurrentTime(),this.options.getDuration())}restart(){this.stop(),this.start()}},he=({currentIndex:e,element:t,formatTime:n,getDisplay:r,getDuration:i,tracks:a})=>{t.replaceChildren(...a.map((e,t)=>{let a=document.createElement(`option`),o=r(e),s=i(e),c=s===void 0?`--:--`:n(s);return a.value=String(t),a.textContent=`${S(o)} (${c})`,a})),t.value=String(Math.max(0,Math.min(e,a.length-1)))},ge=`left`,_e=`scroll`,ve=`always`,T=6,E=`${T}px`,ye=85,be=60,xe=`200px`,Se=`calc(100% - (var(--attr-hspace, 0px) * 2))`,D=`direction`,O=`behavior`,k=`animate`,A=`scrollamount`,j=`scrolldelay`,M=`truespeed`,N=`loop`,P=`bgcolor`,F=`width`,I=`height`,L=`hspace`,R=`vspace`,z=`--attr-width`,B=`--attr-height`,V=`--attr-hspace`,H=`--attr-vspace`,U=`--attr-bgcolor`,Ce=globalThis.HTMLElement??class{},W=e=>{if(e===null)return null;let t=e.trim();return t===``?null:/^[+-]?(?:\d+|\d*\.\d+)$/.test(t)?`${t}px`:globalThis.CSS?.supports(`width`,t)?t:null},we=e=>{if(e===null)return null;let t=e.trim();return t===``?null:/^[+-]?(?:\d+|\d*\.\d+)$/.test(t)?Number(t)>=0?`${t}px`:null:globalThis.CSS?.supports(`width`,t)?t:null},Te=[{attribute:F,cssVar:z,parser:W},{attribute:I,cssVar:B,parser:W,fallback(e){return e.isVerticalDirection&&!e.hasAttribute(I)?xe:null}},{attribute:L,cssVar:V,parser:W},{attribute:R,cssVar:H,parser:W},{attribute:P,cssVar:U,parser:e=>{if(e===null)return null;let t=e.trim();return t===``?null:globalThis.CSS?.supports(`background-color`,t)?t:null}}],G=class extends Ce{static observedAttributes=[D,O,k,A,j,M,N,P,F,I,L,R];track;scrollAmountProbe;tickInterval=null;currentPosition=0;currentStepDelta=0;completedIterations=0;hasPosition=!1;running=!1;constructor(){super();let e=this.attachShadow({mode:`open`});e.innerHTML=` <style> :host { display: inline-block; text-align: initial; overflow: hidden !important; white-space: nowrap; width: var(${z}, ${Se}); height: var(${B}, auto); margin-inline: var(${V}, 0px); margin-block: var(${H}, 0px); background-color: var(${U}, transparent); box-sizing: border-box; } :host([direction="up"]), :host([direction="down"]) { white-space: normal; } .track { display: inline-block; transform: translate(0px, 0px); will-change: transform; } .scrollamount-probe { block-size: 0; display: block; inline-size: ${E}; overflow: hidden; pointer-events: none; position: absolute; visibility: hidden; } </style> <span class="track"><slot></slot></span> <span class="scrollamount-probe" aria-hidden="true"></span> `;let t=e.querySelector(`.track`);if(!t)throw Error(`Remarqueeble track element was not created.`);let n=e.querySelector(`.scrollamount-probe`);if(!n)throw Error(`Remarqueeble scrollamount probe was not created.`);this.track=t,this.scrollAmountProbe=n}connectedCallback(){this.running=!0,this.syncPresentationalHints(),requestAnimationFrame(()=>{!this.isConnected||!this.running||this.reset()})}disconnectedCallback(){this.running=!1,this.clearTickInterval()}attributeChangedCallback(e,t,n){t!==n&&(this.syncPresentationalHints(),this.isConnected&&(this.hasPosition=!1,this.completedIterations=0,this.reset()))}get direction(){return this.getAttribute(D)||ge}get behavior(){return this.getAttribute(O)||_e}get scrollAmount(){let e=we(this.getAttribute(A))??E;this.scrollAmountProbe.style.inlineSize=e;let t=this.scrollAmountProbe.getBoundingClientRect().width;return Number.isFinite(t)&&t>=0?t:T}get scrollDelay(){let e=this.getAttribute(j),t=e===null||e.trim()===``?NaN:Number(e),n=Number.isFinite(t)&&t>=0?t:ye;return this.hasAttribute(M)?n:Math.max(n,be)}get loop(){let e=this.getAttribute(N);return e===null?-1:Number(e)}get directionSign(){return this.direction===`right`||this.direction===`down`?1:-1}get isVerticalDirection(){return this.direction===`up`||this.direction===`down`}start(){this.running||(this.running=!0,this.reset())}stop(){this.running=!1,this.clearTickInterval()}syncPresentationalHints(){for(let e of Te)this.syncVar(e)}syncVar(e){let t=this.getAttribute(e.attribute),n=e.parser(t),r=e.fallback?e.fallback(this):null;if(n==null){r==null?this.style.removeProperty(e.cssVar):this.style.setProperty(e.cssVar,r);return}this.style.setProperty(e.cssVar,n)}reset(){let e=this.syncGeometry();if(this.currentStepDelta=e.stepDelta,this.completedIterations=0,!e.canAnimate){this.syncInactiveState(),this.ensureTicking();return}this.currentPosition=e.startPosition,this.hasPosition=!0,this.syncActiveState(),this.applyCurrentPosition(),this.emitLegacyEvent(`start`),this.restartTicking()}getHostSize(){return this.isVerticalDirection?this.clientHeight:this.clientWidth}getTrackSize(){return this.isVerticalDirection?this.track.offsetHeight:this.track.offsetWidth}getStartPosition(e,t){return this.directionSign<0?e:-t}getFlushEndPosition(e,t){return this.directionSign<0?0:e-t}getOffEndPosition(e,t){return this.directionSign<0?-t:e}getSlideEndPosition(e,t){return this.directionSign<0?Math.min(0,e-t):Math.max(0,e-t)}getAlternateStartPosition(e,t){return this.directionSign<0?e-t:0}syncGeometry(){let e=this.getHostSize(),t=this.getTrackSize(),n=this.shouldAnimate(e,t)&&this.scrollAmount!==0,r=this.behavior===`alternate`?this.getAlternateStartPosition(e,t):this.getStartPosition(e,t),i=this.behavior===`slide`?this.getSlideEndPosition(e,t):this.behavior===`alternate`?this.getFlushEndPosition(e,t):this.getOffEndPosition(e,t);return Math.abs(i-r),{canAnimate:n,endPosition:i,hostSize:e,startPosition:r,stepDelta:this.directionSign<0?-this.scrollAmount:this.scrollAmount,trackSize:t}}shouldAnimate(e,t){return this.animationMode===`never`?!1:this.animationMode!==`overflow`||t>e}get animationMode(){let e=this.getAttribute(k);return e===`overflow`||e===`never`?e:ve}clearTickInterval(){this.tickInterval!==null&&(clearInterval(this.tickInterval),this.tickInterval=null)}hasFiniteAnimation(){return this.behavior===`slide`&&!this.hasAttribute(N)||this.hasAttribute(N)&&Number.isFinite(this.loop)&&this.loop>0}ensureTicking(){!this.running||this.tickInterval!==null||(this.tickInterval=setInterval(()=>{this.tick()},this.scrollDelay))}restartTicking(){this.clearTickInterval(),this.ensureTicking()}tick(){if(!this.running)return;let e=this.syncGeometry();if(!e.canAnimate){this.syncInactiveState();return}if(this.syncActiveState(),!this.hasPosition)this.currentStepDelta=e.stepDelta,this.currentPosition=e.startPosition,this.completedIterations=0,this.hasPosition=!0,this.emitLegacyEvent(`start`);else{if(this.behavior===`alternate`){let t=Math.sign(this.currentStepDelta||e.stepDelta)||1;this.currentStepDelta=Math.abs(e.stepDelta)*t}else this.currentStepDelta=e.stepDelta;this.clampCurrentPosition(e)}if(this.behavior===`alternate`?this.stepAlternate(e.hostSize,e.trackSize):this.stepLinear(e.hostSize,e.trackSize),this.applyCurrentPosition(),!this.running){this.clearTickInterval();return}}stepLinear(e,t){let n=this.getStartPosition(e,t),r=this.behavior===`slide`?this.getSlideEndPosition(e,t):this.getOffEndPosition(e,t),i=this.currentPosition+this.currentStepDelta;if(this.directionSign<0){if(i>r){this.currentPosition=i;return}let e=r-i;if(this.completedIterations+=1,this.hasCompletedIterations()){this.completeFiniteAnimation(r);return}this.currentPosition=this.behavior===`slide`?n:n-e;return}if(i<r){this.currentPosition=i;return}let a=i-r;if(this.completedIterations+=1,this.hasCompletedIterations()){this.completeFiniteAnimation(r);return}this.currentPosition=this.behavior===`slide`?n:n+a}stepAlternate(e,t){let n=Math.min(this.getAlternateStartPosition(e,t),this.getFlushEndPosition(e,t)),r=Math.max(this.getAlternateStartPosition(e,t),this.getFlushEndPosition(e,t)),i=this.currentPosition+this.currentStepDelta;for(;i<n||i>r;)if(i=i<n?n+(n-i):r-(i-r),this.currentStepDelta*=-1,this.completedIterations+=1,this.emitLegacyEvent(`bounce`),this.hasCompletedIterations()){this.completeFiniteAnimation(this.currentStepDelta>0?n:r);return}this.currentPosition=i}hasCompletedIterations(){return this.hasFiniteAnimation()&&this.completedIterations>=Math.max(1,this.loop)}completeFiniteAnimation(e){this.currentPosition=e,this.running=!1,this.emitLegacyEvent(`finish`)}syncInactiveState(){this.track.style.transform=`translate(0px, 0px)`,this.currentPosition=0,this.currentStepDelta=0,this.completedIterations=0,this.hasPosition=!1}syncActiveState(){this.track.style.removeProperty(`animation-name`)}clampCurrentPosition(e){if(this.behavior===`alternate`){let t=Math.min(e.startPosition,e.endPosition),n=Math.max(e.startPosition,e.endPosition);this.currentPosition=Math.min(n,Math.max(t,this.currentPosition));return}if(this.behavior===`slide`){let t=Math.min(e.startPosition,e.endPosition),n=Math.max(e.startPosition,e.endPosition);this.currentPosition=Math.min(n,Math.max(t,this.currentPosition))}}applyCurrentPosition(){if(this.isVerticalDirection){this.track.style.transform=`translate(0px, ${this.currentPosition}px)`;return}this.track.style.transform=`translate(${this.currentPosition}px, 0px)`}emitLegacyEvent(e){this.dispatchEvent(new Event(e))}},Ee=class extends G{},De=class extends G{},Oe=()=>{typeof customElements>`u`||(customElements.get(`re-marquee`)||customElements.define(`re-marquee`,Ee),customElements.get(`re-marquee-ble`)||customElements.define(`re-marquee-ble`,De))},ke=class e extends s{static observedAttributes=[`src`,c,l,u,d,h,g,f,p,m];static reconnectGraceMs=1e3;dom;metadataController;progressController;trackElements=new WeakMap;tracks=[];index=0;desiredPlaying=!1;playing=!1;ready=!1;trackLoadId=0;duration=0;activePlayableTrack=null;backendRegistrationCleanup=null;restartOnNextPlay=!1;trackObserver=null;playlistOverride=null;remotePlaylist=null;remotePlaylistError=``;remotePlaylistLoading=!1;remotePlaylistRequestId=0;remotePlaylistSource=``;remotePlaylistSelected=!1;loadedTrackKey=``;statusMessage=``;timeMode=`elapsed`;disconnectTeardownId=null;constructor(){super(),Oe(),typeof MutationObserver<`u`&&(this.trackObserver=new MutationObserver(()=>this.syncChildTracks())),this.dom=de(this),this.syncDisplayMarqueeMode(),this.syncTrackPickerVisibility(),this.metadataController=new fe({getHost:()=>this,getPreloadMetadata:()=>this.preloadMetadata,getTrackElement:e=>this.trackElements.get(e)??null,getTrackKey:e=>this.getTrackKey(e),getTracks:()=>this.tracks,isCurrentTrack:e=>this.isCurrentTrack(e),onCurrentTrackDisplayChange:()=>this.renderCurrentTrack(),onPlaylistDisplayChange:()=>this.renderTrackSelect(),trackPrefersMediaMetadata:e=>this.trackPrefersMediaMetadata(e)}),this.progressController=new me({dom:this.dom,getCurrentTime:()=>this.getCurrentTime(),getDuration:()=>this.duration,getPlaying:()=>this.playing,getTimeMode:()=>this.timeMode,onStatusChange:(e=``)=>this.updateStatus(e)}),this.dom.playButton.addEventListener(`click`,()=>this.toggle()),this.dom.timeButton.addEventListener(`click`,()=>this.toggleTimeMode()),this.dom.trackSelect.addEventListener(`change`,()=>this.selectTrackFromInput()),this.dom.trackSelect.addEventListener(`keyup`,e=>this.handleTrackSelectKeyup(e)),this.dom.seekInput.addEventListener(`input`,()=>this.seekFromInput()),this.dom.audio.addEventListener(`loadedmetadata`,()=>this.syncAudio()),this.dom.audio.addEventListener(`timeupdate`,()=>this.syncAudio()),this.dom.audio.addEventListener(`ended`,()=>this.finishTrack())}connectedCallback(){if(this.disconnectTeardownId!==null&&(window.clearTimeout(this.disconnectTeardownId),this.disconnectTeardownId=null),this.backendRegistrationCleanup=a(()=>this.handleBackendRegistration()),this.trackObserver?.observe(this,{attributeFilter:[ne,d,ee,`src`,te,re],attributes:!0,childList:!0,subtree:!0}),this.hasRemotePlaylistSource()&&this.syncRemotePlaylist(),this.canResumeConnectedTrack()){this.restoreConnectedTrack();return}this.syncTracks(),this.loadTrack()}disconnectedCallback(){this.backendRegistrationCleanup?.(),this.backendRegistrationCleanup=null,this.trackObserver?.disconnect(),this.stopProgressLoop(),this.disconnectTeardownId=window.setTimeout(()=>{this.isConnected||(this.activePlayableTrack?.stop(),this.activePlayableTrack=null,this.disconnectTeardownId=null)},e.reconnectGraceMs)}attributeChangedCallback(e,t,n){if(t!==n){if(e===`preload-metadata`||e===`prefer-media-metadata`){this.renderCurrentTrack(),this.renderTrackSelect(),this.preloadPlaylistMetadata();return}if(e===`display-marquee`){this.syncDisplayMarqueeMode();return}if(e===`show-track-select`){this.syncTrackPickerVisibility();return}if(e===`show-source-link`){this.syncSourceLink(this.currentTrack);return}e!==`midi-oscillator`&&(e===`playlist-src`&&this.isConnected&&this.syncRemotePlaylist(),this.syncTracks(),this.loadTrack())}}get currentTrack(){return this.tracks[this.index]??null}get currentTrackIndex(){return this.index}get currentTime(){return this.getCurrentTime()}set currentTime(e){this.seek(e)}get playlist(){return[...this.tracks]}get totalTracks(){return this.tracks.length}get preloadMetadata(){return this.hasAttribute(u)}set preloadMetadata(e){this.toggleAttribute(u,e)}get preferMediaMetadata(){return this.hasAttribute(d)}set preferMediaMetadata(e){this.toggleAttribute(d,e)}get showTrackSelect(){return _(this.getAttribute(`show-track-select`))??!0}set showTrackSelect(e){if(e){this.removeAttribute(g);return}this.setAttribute(g,`false`)}get showSourceLink(){return this.hasAttribute(h)}set showSourceLink(e){this.toggleAttribute(h,e)}get displayMarquee(){return x(this.getAttribute(f))}set displayMarquee(e){this.setAttribute(f,x(e))}get midiOscillator(){return le(this.getAttribute(p))}set midiOscillator(e){this.setAttribute(p,e)}set playlist(e){this.playlistOverride=e.map(e=>y(e)).filter(e=>e!==null),this.tracks=[...this.playlistOverride],this.index=0,this.renderTrackSelect(),this.preloadPlaylistMetadata(),this.loadTrack()}async play(){if(!this.currentTrack||!this.ready)return;this.desiredPlaying=!0;let e=this.trackLoadId,t=await this.activePlayableTrack?.play({isStale:()=>e!==this.trackLoadId,restart:this.restartOnNextPlay});this.restartOnNextPlay=!1,e===this.trackLoadId&&(t&&(this.playing=!0),this.syncPlayingState(),t&&this.emitJuketteEvent(`jukette:play`))}pause(){let e=this.playing||this.desiredPlaying;this.setStatus(),this.desiredPlaying=!1,this.activePlayableTrack?.pause(),this.playing=!1,this.syncPlayingState(),e&&this.emitJuketteEvent(`jukette:pause`)}toggle(){if(this.playing){this.pause();return}this.play()}seek(e){!this.currentTrack||!this.ready||(this.setStatus(`Seeking`),this.activePlayableTrack?.seek(e),this.playing&&this.play(),this.syncProgress(e,this.duration),this.emitJuketteEvent(`jukette:seek`),window.setTimeout(()=>{(this.playing||!this.desiredPlaying)&&this.setStatus()},500))}getCurrentTime(){return this.currentTrack?this.activePlayableTrack?.currentTime??0:0}getJuketteEventDetail(e={}){return ce({currentTime:this.getCurrentTime(),duration:this.duration,index:this.index,playing:this.playing,track:this.currentTrack,tracks:this.tracks,...e})}emitJuketteEvent(e,t={}){typeof CustomEvent>`u`||this.dispatchEvent(new CustomEvent(e,{bubbles:!0,composed:!0,detail:this.getJuketteEventDetail(t)}))}syncTracks(){let e=this.getChildTracks(),t=oe(this.getAttribute(c)),n=this.remotePlaylist??[],r=y(this.getAttribute(`src`)??void 0);this.remotePlaylistSelected=this.playlistOverride===null&&e.length===0&&t.length===0&&this.hasRemotePlaylistSource(),this.tracks=this.playlistOverride??(e.length>0?e:t.length>0?t:n.length>0?n:r?[r]:[]);let i=Number(this.getAttribute(m));this.index=Math.min(Number.isInteger(i)&&i>=0?i:this.index,Math.max(0,this.tracks.length-1)),this.renderTrackSelect(),this.preloadPlaylistMetadata()}syncChildTracks(){if(this.playlistOverride)return;let e=this.currentTrack;if(this.syncTracks(),this.currentTrack?.src===e?.src){this.renderTrackSelect();return}this.loadTrack()}getChildTracks(){return Array.from(this.children).flatMap(e=>{let t=se(e);return t?(this.trackElements.set(t,e),[t]):[]})}createPlayableTrack(e){let t=this.createPlayableCallbacks(e),n=o(e);return n?n.createPlayableTrack(e,t,{audioElement:this.dom.audio,getMidiOscillator:()=>this.midiOscillator,host:this,trackElement:this.trackElements.get(e)??null}):null}createPlayableCallbacks(e){let t=()=>this.currentTrack!==null&&this.getTrackKey(this.currentTrack)===this.getTrackKey(e);return{onDuration:n=>{this.metadataController.setDuration(e,n),t()&&(this.duration=n,this.syncProgress(this.getCurrentTime(),this.duration))},onFinish:()=>{t()&&this.finishTrack()},onMetadata:(t,n)=>{n!==void 0&&n!==this.metadataController.metadataPreloadId||this.metadataController.setMetadata(e,t)},onPause:()=>{if(!t())return;let e=this.playing||this.desiredPlaying;this.desiredPlaying=!1,this.playing=!1,this.syncPlayingState(),e&&this.emitJuketteEvent(`jukette:pause`)},onPlay:()=>{t()&&(this.desiredPlaying=!0,this.playing=!0,this.syncPlayingState(),this.emitJuketteEvent(`jukette:play`))},onProgress:(e,n)=>{t()&&this.syncProgress(e,n)},onReady:()=>{t()&&(this.setReady(!0),this.playing||this.setStatus())},onStatus:(e=``)=>{t()&&this.setStatus(e)}}}loadTrack(){this.trackLoadId+=1;let e=this.loadedTrackKey;this.activePlayableTrack?.stop(),this.activePlayableTrack=null,this.desiredPlaying=!1,this.playing=!1,this.setReady(!1),this.duration=0,this.syncProgress(0,0);let t=this.currentTrack;if(!t){this.loadedTrackKey=``,this.statusMessage=``,this.renderDisplay(this.getEmptyTrackDisplayText()),this.setReady(!1),this.dom.trackSelect.disabled=!0,e&&this.emitJuketteEvent(`jukette:trackchange`);return}let n=v(t),r=this.getTrackKey(t);if(this.loadedTrackKey=r,this.duration=this.getTrackDuration(t)??0,this.dataset.kind=n,this.setReady(!1),this.dom.trackSelect.disabled=!1,this.renderCurrentTrack(),this.setStatus(`Preparing ${n}`),this.syncProgress(0,this.duration),this.activePlayableTrack=this.createPlayableTrack(t),!this.activePlayableTrack){this.setStatus(`${n} playback unavailable`),this.renderTrackSelect(),this.syncPlayingState(),r!==e&&this.emitJuketteEvent(`jukette:trackchange`);return}this.activePlayableTrack.load({metadataPreloadId:this.metadataController.metadataPreloadId,restart:this.restartOnNextPlay}),this.renderTrackSelect(),this.syncPlayingState(),r!==e&&this.emitJuketteEvent(`jukette:trackchange`)}renderTrackSelect(){he({currentIndex:this.index,element:this.dom.trackSelect,formatTime:w,getDisplay:e=>this.getTrackDisplay(e),getDuration:e=>this.getTrackDuration(e),tracks:this.tracks})}selectTrack(e){if(this.restartOnNextPlay=!0,e===this.index){this.loadTrack();return}this.index=e,this.loadTrack()}selectTrackFromInput(){let e=Number(this.dom.trackSelect.value);!Number.isInteger(e)||e<0||this.selectTrack(e)}handleTrackSelectKeyup(e){if(!(e.key!==`Enter`&&e.key!==` `)&&!(!this.ready||this.dom.trackSelect.disabled)){if(e.key===`Enter`){this.toggle();return}this.playing||this.play()}}canResumeConnectedTrack(){let e=this.currentTrack;return!e||!this.activePlayableTrack?!1:this.loadedTrackKey===this.getTrackKey(e)}restoreConnectedTrack(){this.dom.trackSelect.disabled=!1,this.renderCurrentTrack(),this.renderTrackSelect(),this.syncProgress(this.getCurrentTime(),this.duration),this.syncPlayingState(),this.playing||this.setStatus()}getTrackDuration(e){return this.metadataController.getDuration(e)}getTrackKey(e){return`${v(e)}:${e.src}`}isCurrentTrack(e){return this.currentTrack!==null&&this.getTrackKey(this.currentTrack)===this.getTrackKey(e)}trackPrefersMediaMetadata(e){return e.preferMediaMetadata??this.preferMediaMetadata}getTrackDisplay(e){return this.metadataController.getDisplay(e)}renderCurrentTrack(){let e=this.currentTrack;if(!e)return;let t=this.getTrackDisplay(e);this.renderDisplay(this.statusMessage||S(t),e)}preloadPlaylistMetadata(){this.metadataController.preloadPlaylistMetadata()}toggleTimeMode(){this.ready&&(this.timeMode=this.timeMode===`elapsed`?`remaining`:`elapsed`,this.syncProgress(this.getCurrentTime(),this.duration),this.playing&&this.progressController.restart())}seekFromInput(){!this.ready||!this.duration||this.seek(Number(this.dom.seekInput.value)/1e3*this.duration)}syncAudio(){this.activePlayableTrack?.requestPosition(()=>!1),this.playing||this.setStatus()}syncProgress(e,t){this.progressController.syncProgress(e,t)}syncPlayingState(){this.progressController.syncPlayingState()}setStatus(e=``){this.progressController.setStatus(e)}updateStatus(e=``){this.statusMessage=e;let t=this.currentTrack;if(!t){this.renderDisplay(e||`No track`);return}this.renderDisplay(e||S(this.getTrackDisplay(t)),t)}renderDisplay(e,t=null){this.dom.displayElement.textContent=e,this.dom.displayElement.stop(),this.dom.displayElement.start(),this.syncSourceLink(t)}syncSourceLink(e){let t=this.getTrackSourceUrl(e);if(!t){this.dom.sourceLink.hidden=!0,this.dom.sourceLink.removeAttribute(`href`);return}this.dom.sourceLink.href=t,this.dom.sourceLink.hidden=!1}getTrackSourceUrl(e){return!e||!this.trackShowsSourceLink(e)?null:e.src}trackShowsSourceLink(e){return e.showSourceLink??this.showSourceLink}syncDisplayMarqueeMode(){this.dom.displayElement.setAttribute(`animate`,this.displayMarquee)}syncTrackPickerVisibility(){this.dom.trackPicker.hidden=!this.showTrackSelect}finishTrack(){this.desiredPlaying=!1,this.playing=!1,this.restartOnNextPlay=!0,this.syncPlayingState(),this.syncProgress(this.duration,this.duration),this.emitJuketteEvent(`jukette:ended`)}setReady(e){this.ready=e,this.dom.playButton.disabled=!e,this.dom.seekInput.disabled=!e,this.dom.timeButton.disabled=!e}stopProgressLoop(){this.progressController.stop()}handleBackendRegistration(){this.preloadPlaylistMetadata(),this.renderTrackSelect();let e=this.currentTrack;!e||this.activePlayableTrack||o(e)&&this.loadTrack()}getEmptyTrackDisplayText(){return this.remotePlaylistSelected?this.remotePlaylistLoading?`Loading playlist`:this.remotePlaylistError?this.remotePlaylistError:`No track`:`No track`}hasRemotePlaylistSource(){return(this.getAttribute(`playlist-src`)??``).trim().length>0}async syncRemotePlaylist(){let e=(this.getAttribute(`playlist-src`)??``).trim();if(!e){this.remotePlaylistRequestId+=1,this.remotePlaylistSource=``,this.remotePlaylist=null,this.remotePlaylistError=``,this.remotePlaylistLoading=!1,this.syncTracks(),this.loadTrack();return}if(e===this.remotePlaylistSource&&(this.remotePlaylistLoading||this.remotePlaylist!==null||this.remotePlaylistError!==``))return;let t=++this.remotePlaylistRequestId;if(this.remotePlaylistSource=e,this.remotePlaylist=null,this.remotePlaylistError=``,this.remotePlaylistLoading=!0,this.syncTracks(),this.loadTrack(),typeof fetch>`u`){if(t!==this.remotePlaylistRequestId)return;this.remotePlaylistLoading=!1,this.remotePlaylistError=`Playlist fetch unavailable`,this.syncTracks(),this.loadTrack();return}try{let n=await fetch(e);if(!n.ok)throw Error(`Playlist request failed: ${n.status}`);let r=b(JSON.parse(await n.text()));if(t!==this.remotePlaylistRequestId)return;this.remotePlaylist=r,this.remotePlaylistError=``,this.remotePlaylistLoading=!1}catch{if(t!==this.remotePlaylistRequestId)return;this.remotePlaylist=null,this.remotePlaylistError=`Playlist failed to load`,this.remotePlaylistLoading=!1}this.syncTracks(),this.loadTrack()}},Ae=()=>{typeof customElements>`u`||(customElements.get(`jukette-track`)||customElements.define(`jukette-track`,je),customElements.get(`jukette-player`)||customElements.define(`jukette-player`,ke))},je=class extends s{},Me=class{track;callbacks;durationValue=0;constructor(e,t){this.track=e,this.callbacks=t}get currentTime(){return 0}get duration(){return this.durationValue}load(e){}seek(e){}stop(){this.pause({silent:!0})}requestPosition(e){}},K=e=>String.fromCharCode(...e),q=e=>String.fromCharCode(...e),Ne=e=>{let t=[];for(let n=0;n+1<e.length;n+=2)t.push(e[n]<<8|e[n+1]);return String.fromCharCode(...t)},J=(e,t)=>{try{return new TextDecoder(t).decode(e)}catch{return t===`iso-8859-1`?q(e):K(e)}},Pe=e=>{let t=e.indexOf(`\0`);return(t>=0?e.slice(0,t):e.trimEnd()).trim()},Y=(e,t,n=4)=>{let r=0;for(let i=0;i<n;i++)r=r<<7|e[t+i]&127;return r},X=(e,t)=>(e[t]<<24|e[t+1]<<16|e[t+2]<<8|e[t+3])>>>0,Z=e=>{if(e.length<2)return``;let t=e[0],n=e.slice(1);return Pe(t===0?q(n):t===3?J(n,`utf-8`):t===2?Ne(n):J(n,`utf-16`))},Q=e=>{let t=new Uint8Array(e);if(t.length<10||K(t.slice(0,3))!==`ID3`)return{};let n=t[3],r=t[5],i=Math.min(t.length,10+Y(t,6)),a=10;if(r&64&&a+4<=i){let e=n===4?Y(t,a):X(t,a)+4;a+=e}let o={};for(;a+10<=i;){let e=K(t.slice(a,a+4));if(!/^[A-Z0-9]{4}$/.test(e))break;let r=n===4?Y(t,a+4):X(t,a+4),s=a+10,c=s+r;if(r<=0||c>i)break;let l=t.slice(s,c);e===`TIT2`&&(o.title=Z(l)),e===`TPE1`&&(o.artist=Z(l)),a=c}return o},$=e=>Number.isFinite(e.startAt)?Math.max(0,e.startAt):0,Fe=class extends Me{audio;constructor(e,t,n){super(e,n),this.audio=t}get currentTime(){return this.audio.currentTime}get duration(){return Number.isFinite(this.audio.duration)?this.audio.duration:0}load(e){this.callbacks.onStatus(`Loading audio`);let t=()=>{this.audio.removeEventListener(`canplay`,t),this.callbacks.onReady(),this.audio.paused||this.callbacks.onStatus()},n=()=>{this.audio.removeEventListener(`canplay`,t),this.audio.removeEventListener(`error`,n),this.callbacks.onStatus(`Audio failed to load`)};this.audio.removeEventListener(`canplay`,t),this.audio.removeEventListener(`error`,n),this.audio.addEventListener(`canplay`,t,{once:!0}),this.audio.addEventListener(`error`,n,{once:!0}),this.audio.src=this.track.src,this.audio.load(),this.audio.currentTime=$(this.track),this.callbacks.onProgress(this.audio.currentTime,this.duration),this.preloadFileMetadata(e.metadataPreloadId)}async play(e){return e.restart&&(this.audio.currentTime=$(this.track)),this.callbacks.onStatus(`Starting audio`),await this.audio.play(),!e.isStale()}pause(){this.audio.pause()}seek(e){this.audio.currentTime=e}stop(){this.audio.pause()}requestPosition(){this.durationValue=this.duration,this.callbacks.onDuration(this.durationValue),this.callbacks.onProgress(this.audio.currentTime,this.durationValue)}async preloadFileMetadata(e){if(!(typeof fetch>`u`))try{let t=await fetch(this.track.src,{headers:{Range:`bytes=0-65535`}});if(!t.ok)return;this.callbacks.onMetadata(Q(await t.arrayBuffer()),e)}catch{}}};i({createPlayableTrack(e,t,n){return new Fe(e,n.audioElement,t)},inferTrackType(e){return/\.(?:mid|midi)(?:[?#].*)?$/i.test(e.src)?null:`audio`},preloadTrack:async(e,t)=>{let n={};if(t.preloadDuration&&typeof Audio<`u`){let t=await new Promise(t=>{let n=new Audio,r=()=>{n.removeEventListener(`loadedmetadata`,a),n.removeEventListener(`error`,i),n.removeAttribute(`src`),n.load()},i=()=>{r(),t(void 0)},a=()=>{let e=Number.isFinite(n.duration)?n.duration:void 0;r(),t(e)};n.preload=`metadata`,n.addEventListener(`loadedmetadata`,a),n.addEventListener(`error`,i,{once:!0}),n.src=e.src,n.load()});t&&(n.duration=t)}if(t.preloadMetadata&&typeof fetch<`u`){let t=await fetch(e.src,{headers:{Range:`bytes=0-65535`}});t.ok&&(n.metadata=Q(await t.arrayBuffer()))}return n.duration||n.metadata?n:void 0},type:`audio`}),Ae()})();