@hyperframes/player 0.3.1 → 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.
- package/dist/hyperframes-player.cjs +2 -2
- package/dist/hyperframes-player.cjs.map +1 -1
- package/dist/hyperframes-player.d.cts +1 -0
- package/dist/hyperframes-player.d.ts +1 -0
- package/dist/hyperframes-player.global.js +2 -2
- package/dist/hyperframes-player.global.js.map +1 -1
- package/dist/hyperframes-player.js +1 -1
- package/dist/hyperframes-player.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var w=Object.defineProperty;var F=Object.getOwnPropertyDescriptor;var j=Object.getOwnPropertyNames;var z=Object.prototype.hasOwnProperty;var U=(o,e)=>{for(var t in e)w(o,t,{get:e[t],enumerable:!0})},W=(o,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of j(e))!z.call(o,i)&&i!==t&&w(o,i,{get:()=>e[i],enumerable:!(s=F(e,i))||s.enumerable});return o};var $=o=>W(w({},"__esModule",{value:!0}),o);var q={};U(q,{HyperframesPlayer:()=>x,SPEED_PRESETS:()=>k,formatSpeed:()=>v,formatTime:()=>E});module.exports=$(q);var
|
|
1
|
+
"use strict";var w=Object.defineProperty;var F=Object.getOwnPropertyDescriptor;var j=Object.getOwnPropertyNames;var z=Object.prototype.hasOwnProperty;var U=(o,e)=>{for(var t in e)w(o,t,{get:e[t],enumerable:!0})},W=(o,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of j(e))!z.call(o,i)&&i!==t&&w(o,i,{get:()=>e[i],enumerable:!(s=F(e,i))||s.enumerable});return o};var $=o=>W(w({},"__esModule",{value:!0}),o);var q={};U(q,{HyperframesPlayer:()=>x,SPEED_PRESETS:()=>k,formatSpeed:()=>v,formatTime:()=>E});module.exports=$(q);var H=`
|
|
2
2
|
:host {
|
|
3
3
|
display: block;
|
|
4
4
|
position: relative;
|
|
@@ -195,5 +195,5 @@
|
|
|
195
195
|
color: var(--hfp-accent, #fff);
|
|
196
196
|
font-weight: 600;
|
|
197
197
|
}
|
|
198
|
-
`,C='<svg width="24" height="24" viewBox="0 0 18 18" fill="currentColor"><polygon points="4,2 16,9 4,16"/></svg>',H='<svg width="24" height="24" viewBox="0 0 18 18" fill="currentColor"><rect x="3" y="2" width="4" height="14"/><rect x="11" y="2" width="4" height="14"/></svg>';var k=[.25,.5,1,1.5,2,4];function v(o){return Number.isInteger(o)?`${o}x`:`${o}x`}function E(o){if(!Number.isFinite(o)||o<0)return"0:00";let e=Math.floor(o),t=Math.floor(e/60),s=e%60;return`${t}:${s.toString().padStart(2,"0")}`}function D(o,e,t={}){let s=t.speedPresets??k,i=document.createElement("div");i.className="hfp-controls",i.addEventListener("click",r=>{r.stopPropagation()});let a=document.createElement("button");a.className="hfp-play-btn",a.type="button",a.innerHTML=C,a.setAttribute("aria-label","Play");let d=document.createElement("div");d.className="hfp-scrubber";let l=document.createElement("div");l.className="hfp-progress",l.style.width="0%",d.appendChild(l);let c=document.createElement("span");c.className="hfp-time",c.textContent="0:00 / 0:00";let h=document.createElement("div");h.className="hfp-speed-wrap";let p=document.createElement("button");p.className="hfp-speed-btn",p.type="button",p.textContent="1x",p.setAttribute("aria-label","Playback speed");let u=document.createElement("div");u.className="hfp-speed-menu",u.setAttribute("role","menu");for(let r of s){let n=document.createElement("button");n.className="hfp-speed-option",n.type="button",n.setAttribute("role","menuitem"),n.dataset.speed=String(r),n.textContent=v(r),r===1&&n.classList.add("hfp-active"),u.appendChild(n)}h.appendChild(u),h.appendChild(p),i.appendChild(a),i.appendChild(d),i.appendChild(c),i.appendChild(h),o.appendChild(i);let g=!1,b=null,_=s.indexOf(1);_===-1&&(_=0),a.addEventListener("click",r=>{r.stopPropagation(),g?e.onPause():e.onPlay()});let M=r=>{for(let n of u.querySelectorAll(".hfp-speed-option"))n.classList.toggle("hfp-active",n.dataset.speed===String(r))};p.addEventListener("click",r=>{r.stopPropagation();let n=u.classList.toggle("hfp-open");p.setAttribute("aria-expanded",String(n))}),u.addEventListener("click",r=>{r.stopPropagation();let n=r.target.closest(".hfp-speed-option");if(!n)return;let m=parseFloat(n.dataset.speed);_=s.indexOf(m),p.textContent=v(m),M(m),u.classList.remove("hfp-open"),p.setAttribute("aria-expanded","false"),e.onSpeedChange(m)});let P=()=>{u.classList.remove("hfp-open"),p.setAttribute("aria-expanded","false")};document.addEventListener("click",P);let y=r=>{let n=d.getBoundingClientRect(),m=Math.max(0,Math.min(1,(r-n.left)/n.width));e.onSeek(m)},f=!1;d.addEventListener("mousedown",r=>{r.stopPropagation(),f=!0,y(r.clientX)});let L=r=>{f&&y(r.clientX)},T=()=>{f=!1};document.addEventListener("mousemove",L),document.addEventListener("mouseup",T),d.addEventListener("touchstart",r=>{f=!0;let n=r.touches[0];n&&y(n.clientX)},{passive:!0});let S=r=>{if(f){let n=r.touches[0];n&&y(n.clientX)}},I=()=>{f=!1};document.addEventListener("touchmove",S,{passive:!0}),document.addEventListener("touchend",I);let R=()=>{b&&clearTimeout(b),b=setTimeout(()=>{g&&i.classList.add("hfp-hidden")},3e3)},N=o instanceof ShadowRoot?o.host:o;return N.addEventListener("mousemove",()=>{i.classList.remove("hfp-hidden"),R()}),N.addEventListener("mouseleave",()=>{g&&i.classList.add("hfp-hidden")}),{updateTime(r,n){let m=n>0?r/n*100:0;l.style.width=`${m}%`,c.textContent=`${E(r)} / ${E(n)}`},updatePlaying(r){g=r,a.innerHTML=r?H:C,a.setAttribute("aria-label",r?"Pause":"Play"),r?R():i.classList.remove("hfp-hidden")},updateSpeed(r){let n=s.indexOf(r);n!==-1&&(_=n),p.textContent=v(r),M(r)},show(){i.style.display=""},hide(){i.style.display="none"},destroy(){document.removeEventListener("mousemove",L),document.removeEventListener("mouseup",T),document.removeEventListener("touchmove",S),document.removeEventListener("touchend",I),document.removeEventListener("click",P),b&&clearTimeout(b)}}}var A=30,Y="https://cdn.jsdelivr.net/npm/@hyperframes/core/dist/hyperframe.runtime.iife.js",x=class extends HTMLElement{static get observedAttributes(){return["src","width","height","controls","muted","poster","playback-rate","audio-src"]}shadow;container;iframe;posterEl=null;controlsApi=null;resizeObserver;_ready=!1;_duration=0;_currentTime=0;_paused=!0;_compositionWidth=1920;_compositionHeight=1080;_probeInterval=null;_lastUpdateMs=0;_parentMedia=[];constructor(){super(),this.shadow=this.attachShadow({mode:"open"});let e=document.createElement("style");e.textContent=O,this.shadow.appendChild(e),this.container=document.createElement("div"),this.container.className="hfp-container",this.iframe=document.createElement("iframe"),this.iframe.className="hfp-iframe",this.iframe.sandbox.add("allow-scripts","allow-same-origin"),this.iframe.allow="autoplay; fullscreen",this.iframe.referrerPolicy="no-referrer",this.iframe.title="HyperFrames Composition",this.container.appendChild(this.iframe),this.shadow.appendChild(this.container),this.addEventListener("click",t=>{this._isControlsClick(t)||(this._paused?this.play():this.pause())}),this.resizeObserver=new ResizeObserver(()=>this._updateScale()),this._onMessage=this._onMessage.bind(this),this._onIframeLoad=this._onIframeLoad.bind(this)}connectedCallback(){this.resizeObserver.observe(this),window.addEventListener("message",this._onMessage),this.iframe.addEventListener("load",this._onIframeLoad),this.hasAttribute("controls")&&this._setupControls(),this.hasAttribute("poster")&&this._setupPoster(),this.hasAttribute("audio-src")&&this._setupParentAudioFromUrl(this.getAttribute("audio-src")),this.hasAttribute("src")&&(this.iframe.src=this.getAttribute("src"))}disconnectedCallback(){this.resizeObserver.disconnect(),window.removeEventListener("message",this._onMessage),this.iframe.removeEventListener("load",this._onIframeLoad),this._probeInterval&&clearInterval(this._probeInterval),this.controlsApi?.destroy();for(let e of this._parentMedia)e.el.pause(),e.el.src="";this._parentMedia=[]}attributeChangedCallback(e,t,s){switch(e){case"src":s&&(this._ready=!1,this.iframe.src=s);break;case"width":this._compositionWidth=parseInt(s||"1920",10),this._updateScale();break;case"height":this._compositionHeight=parseInt(s||"1080",10),this._updateScale();break;case"controls":s!==null?this._setupControls():(this.controlsApi?.destroy(),this.controlsApi=null);break;case"poster":this._setupPoster();break;case"playback-rate":{let i=parseFloat(s||"1");for(let a of this._parentMedia)a.el.playbackRate=i;this._sendControl("set-playback-rate",{playbackRate:i}),this.controlsApi?.updateSpeed(i),this.dispatchEvent(new Event("ratechange"));break}case"muted":for(let i of this._parentMedia)i.el.muted=s!==null;this._sendControl("set-muted",{muted:s!==null});break;case"audio-src":s&&this._setupParentAudioFromUrl(s);break}}get iframeElement(){return this.iframe}play(){this._hidePoster(),this._playParentMedia(),this._sendControl("play"),this._paused=!1,this.controlsApi?.updatePlaying(!0),this.dispatchEvent(new Event("play"))}pause(){this._pauseParentMedia(),this._sendControl("pause"),this._paused=!0,this.controlsApi?.updatePlaying(!1),this.dispatchEvent(new Event("pause"))}seek(e){let t=Math.round(e*A);this._sendControl("seek",{frame:t}),this._currentTime=e;for(let s of this._parentMedia){let i=e-s.start;i>=0&&i<s.duration&&(s.el.currentTime=i)}this._paused=!0,this.controlsApi?.updatePlaying(!1),this.controlsApi?.updateTime(this._currentTime,this._duration)}get currentTime(){return this._currentTime}set currentTime(e){this.seek(e)}get duration(){return this._duration}get paused(){return this._paused}get ready(){return this._ready}get playbackRate(){return parseFloat(this.getAttribute("playback-rate")||"1")}set playbackRate(e){this.setAttribute("playback-rate",String(e))}get muted(){return this.hasAttribute("muted")}set muted(e){e?this.setAttribute("muted",""):this.removeAttribute("muted")}get loop(){return this.hasAttribute("loop")}set loop(e){e?this.setAttribute("loop",""):this.removeAttribute("loop")}_sendControl(e,t={}){try{this.iframe.contentWindow?.postMessage({source:"hf-parent",type:"control",action:e,...t},"*")}catch{}}_isControlsClick(e){return e.composedPath().some(t=>t instanceof HTMLElement&&t.classList.contains("hfp-controls"))}_onMessage(e){if(e.source!==this.iframe.contentWindow)return;let t=e.data;if(!(!t||t.source!=="hf-preview")){if(t.type==="state"){this._currentTime=(t.frame??0)/A;let s=!this._paused;this._paused=!t.isPlaying,s&&this._paused?this._pauseParentMedia():!s&&!this._paused&&this._playParentMedia();let i=performance.now();(i-this._lastUpdateMs>100||this._paused!==s)&&(this._lastUpdateMs=i,this.controlsApi?.updateTime(this._currentTime,this._duration),this.controlsApi?.updatePlaying(!this._paused),this.dispatchEvent(new CustomEvent("timeupdate",{detail:{currentTime:this._currentTime}}))),this._currentTime>=this._duration&&!this._paused&&(this._pauseParentMedia(),this.loop?(this.seek(0),this.play()):(this._paused=!0,this.controlsApi?.updatePlaying(!1),this.dispatchEvent(new Event("ended"))))}t.type==="timeline"&&t.durationInFrames>0&&Number.isFinite(t.durationInFrames)&&(this._duration=t.durationInFrames/A,this.controlsApi?.updateTime(this._currentTime,this._duration)),t.type==="stage-size"&&t.width>0&&t.height>0&&(this._compositionWidth=t.width,this._compositionHeight=t.height,this._updateScale())}}_runtimeInjected=!1;_onIframeLoad(){let e=0;this._runtimeInjected=!1,this._probeInterval&&clearInterval(this._probeInterval),this._probeInterval=setInterval(()=>{e++;try{let t=this.iframe.contentWindow;if(!t)return;let s=!!(t.__hf||t.__player),i=!!(t.__timelines&&Object.keys(t.__timelines).length>0);if(!s&&i&&!this._runtimeInjected&&e>=5){this._injectRuntime();return}if(this._runtimeInjected&&!s)return;let d=(()=>{if(t.__player&&typeof t.__player.getDuration=="function")return t.__player;if(t.__timelines){let l=Object.keys(t.__timelines);if(l.length>0){let c=this.iframe.contentDocument?.querySelector("[data-composition-id]")?.getAttribute("data-composition-id"),h=c&&c in t.__timelines?c:l[l.length-1],p=t.__timelines[h];return{getDuration:()=>p.duration()}}}return null})();if(d&&d.getDuration()>0){clearInterval(this._probeInterval),this._duration=d.getDuration(),this._ready=!0,this.controlsApi?.updateTime(0,this._duration),this.dispatchEvent(new CustomEvent("ready",{detail:{duration:this._duration}}));let c=this.iframe.contentDocument?.querySelector("[data-composition-id]");if(c){let h=parseInt(c.getAttribute("data-width")||"0",10),p=parseInt(c.getAttribute("data-height")||"0",10);h>0&&p>0&&(this._compositionWidth=h,this._compositionHeight=p,this._updateScale())}this._setupParentMedia(),this.hasAttribute("autoplay")&&this.play();return}}catch{}e>=40&&(clearInterval(this._probeInterval),this.dispatchEvent(new CustomEvent("error",{detail:{message:"Composition timeline not found after 8s"}})))},200)}_injectRuntime(){this._runtimeInjected=!0;try{let e=this.iframe.contentDocument;if(!e)return;let t=e.createElement("script");t.src=Y,t.onload=()=>{},t.onerror=()=>{},(e.head||e.documentElement).appendChild(t)}catch{}}_updateScale(){let e=this.getBoundingClientRect();if(e.width===0||e.height===0)return;let t=Math.min(e.width/this._compositionWidth,e.height/this._compositionHeight);this.iframe.style.width=`${this._compositionWidth}px`,this.iframe.style.height=`${this._compositionHeight}px`,this.iframe.style.transform=`translate(-50%, -50%) scale(${t})`}_setupControls(){if(this.controlsApi)return;let e={onPlay:()=>this.play(),onPause:()=>this.pause(),onSeek:i=>this.seek(i*this._duration),onSpeedChange:i=>{this.playbackRate=i}},t=this.getAttribute("speed-presets"),s=t?t.split(",").map(Number).filter(i=>!isNaN(i)&&i>0):void 0;this.controlsApi=D(this.shadow,e,{speedPresets:s})}_setupPoster(){let e=this.getAttribute("poster");if(!e){this.posterEl?.remove(),this.posterEl=null;return}this.posterEl||(this.posterEl=document.createElement("img"),this.posterEl.className="hfp-poster",this.shadow.appendChild(this.posterEl)),this.posterEl.src=e}_playParentMedia(){for(let e of this._parentMedia)e.el.src&&e.el.play().catch(()=>{})}_pauseParentMedia(){for(let e of this._parentMedia)e.el.pause()}_createParentMedia(e,t,s,i){if(this._parentMedia.some(d=>d.el.src===e))return;let a=t==="video"?document.createElement("video"):new Audio;a.preload="auto",a.src=e,a.load(),a.muted=this.muted,this.playbackRate!==1&&(a.playbackRate=this.playbackRate),this._parentMedia.push({el:a,start:s,duration:i})}_setupParentAudioFromUrl(e){this._createParentMedia(e,"audio",0,1/0)}_setupParentMedia(){try{let e=this.iframe.contentDocument;if(!e)return;let t=e.querySelectorAll("audio[data-start], video[data-start]");for(let s of t){let i=s.getAttribute("src")||s.querySelector("source")?.src;if(!i)continue;let a=parseFloat(s.getAttribute("data-start")||"0"),d=parseFloat(s.getAttribute("data-duration")||"Infinity"),l=s.tagName==="VIDEO"?"video":"audio";this._createParentMedia(i,l,a,d),s.removeAttribute("src"),s.removeAttribute("data-start"),s.removeAttribute("data-duration"),s.querySelectorAll("source").forEach(c=>c.remove())}}catch{}}_hidePoster(){this.posterEl?.remove(),this.posterEl=null}};customElements.get("hyperframes-player")||customElements.define("hyperframes-player",x);0&&(module.exports={HyperframesPlayer,SPEED_PRESETS,formatSpeed,formatTime});
|
|
198
|
+
`,C='<svg width="24" height="24" viewBox="0 0 18 18" fill="currentColor"><polygon points="4,2 16,9 4,16"/></svg>',O='<svg width="24" height="24" viewBox="0 0 18 18" fill="currentColor"><rect x="3" y="2" width="4" height="14"/><rect x="11" y="2" width="4" height="14"/></svg>';var k=[.25,.5,1,1.5,2,4];function v(o){return Number.isInteger(o)?`${o}x`:`${o}x`}function E(o){if(!Number.isFinite(o)||o<0)return"0:00";let e=Math.floor(o),t=Math.floor(e/60),s=e%60;return`${t}:${s.toString().padStart(2,"0")}`}function D(o,e,t={}){let s=t.speedPresets??k,i=document.createElement("div");i.className="hfp-controls",i.addEventListener("click",r=>{r.stopPropagation()});let a=document.createElement("button");a.className="hfp-play-btn",a.type="button",a.innerHTML=C,a.setAttribute("aria-label","Play");let d=document.createElement("div");d.className="hfp-scrubber";let l=document.createElement("div");l.className="hfp-progress",l.style.width="0%",d.appendChild(l);let c=document.createElement("span");c.className="hfp-time",c.textContent="0:00 / 0:00";let h=document.createElement("div");h.className="hfp-speed-wrap";let p=document.createElement("button");p.className="hfp-speed-btn",p.type="button",p.textContent="1x",p.setAttribute("aria-label","Playback speed");let u=document.createElement("div");u.className="hfp-speed-menu",u.setAttribute("role","menu");for(let r of s){let n=document.createElement("button");n.className="hfp-speed-option",n.type="button",n.setAttribute("role","menuitem"),n.dataset.speed=String(r),n.textContent=v(r),r===1&&n.classList.add("hfp-active"),u.appendChild(n)}h.appendChild(u),h.appendChild(p),i.appendChild(a),i.appendChild(d),i.appendChild(c),i.appendChild(h),o.appendChild(i);let g=!1,b=null,_=s.indexOf(1);_===-1&&(_=0),a.addEventListener("click",r=>{r.stopPropagation(),g?e.onPause():e.onPlay()});let M=r=>{for(let n of u.querySelectorAll(".hfp-speed-option"))n.classList.toggle("hfp-active",n.dataset.speed===String(r))};p.addEventListener("click",r=>{r.stopPropagation();let n=u.classList.toggle("hfp-open");p.setAttribute("aria-expanded",String(n))}),u.addEventListener("click",r=>{r.stopPropagation();let n=r.target.closest(".hfp-speed-option");if(!n)return;let m=parseFloat(n.dataset.speed);_=s.indexOf(m),p.textContent=v(m),M(m),u.classList.remove("hfp-open"),p.setAttribute("aria-expanded","false"),e.onSpeedChange(m)});let L=()=>{u.classList.remove("hfp-open"),p.setAttribute("aria-expanded","false")};document.addEventListener("click",L);let y=r=>{let n=d.getBoundingClientRect(),m=Math.max(0,Math.min(1,(r-n.left)/n.width));e.onSeek(m)},f=!1;d.addEventListener("mousedown",r=>{r.stopPropagation(),f=!0,y(r.clientX)});let P=r=>{f&&y(r.clientX)},T=()=>{f=!1};document.addEventListener("mousemove",P),document.addEventListener("mouseup",T),d.addEventListener("touchstart",r=>{f=!0;let n=r.touches[0];n&&y(n.clientX)},{passive:!0});let S=r=>{if(f){let n=r.touches[0];n&&y(n.clientX)}},I=()=>{f=!1};document.addEventListener("touchmove",S,{passive:!0}),document.addEventListener("touchend",I);let R=()=>{b&&clearTimeout(b),b=setTimeout(()=>{g&&i.classList.add("hfp-hidden")},3e3)},N=o instanceof ShadowRoot?o.host:o;return N.addEventListener("mousemove",()=>{i.classList.remove("hfp-hidden"),R()}),N.addEventListener("mouseleave",()=>{g&&i.classList.add("hfp-hidden")}),{updateTime(r,n){let m=n>0?r/n*100:0;l.style.width=`${m}%`,c.textContent=`${E(r)} / ${E(n)}`},updatePlaying(r){g=r,a.innerHTML=r?O:C,a.setAttribute("aria-label",r?"Pause":"Play"),r?R():i.classList.remove("hfp-hidden")},updateSpeed(r){let n=s.indexOf(r);n!==-1&&(_=n),p.textContent=v(r),M(r)},show(){i.style.display=""},hide(){i.style.display="none"},destroy(){document.removeEventListener("mousemove",P),document.removeEventListener("mouseup",T),document.removeEventListener("touchmove",S),document.removeEventListener("touchend",I),document.removeEventListener("click",L),b&&clearTimeout(b)}}}var A=30,Y="https://cdn.jsdelivr.net/npm/@hyperframes/core/dist/hyperframe.runtime.iife.js",x=class extends HTMLElement{static get observedAttributes(){return["src","width","height","controls","muted","poster","playback-rate","audio-src"]}shadow;container;iframe;posterEl=null;controlsApi=null;resizeObserver;_ready=!1;_duration=0;_currentTime=0;_paused=!0;_compositionWidth=1920;_compositionHeight=1080;_probeInterval=null;_lastUpdateMs=0;_parentMedia=[];constructor(){super(),this.shadow=this.attachShadow({mode:"open"});let e=document.createElement("style");e.textContent=H,this.shadow.appendChild(e),this.container=document.createElement("div"),this.container.className="hfp-container",this.iframe=document.createElement("iframe"),this.iframe.className="hfp-iframe",this.iframe.sandbox.add("allow-scripts","allow-same-origin"),this.iframe.allow="autoplay; fullscreen",this.iframe.referrerPolicy="no-referrer",this.iframe.title="HyperFrames Composition",this.container.appendChild(this.iframe),this.shadow.appendChild(this.container),this.addEventListener("click",t=>{this._isControlsClick(t)||(this._paused?this.play():this.pause())}),this.resizeObserver=new ResizeObserver(()=>this._updateScale()),this._onMessage=this._onMessage.bind(this),this._onIframeLoad=this._onIframeLoad.bind(this)}connectedCallback(){this.resizeObserver.observe(this),window.addEventListener("message",this._onMessage),this.iframe.addEventListener("load",this._onIframeLoad),this.hasAttribute("controls")&&this._setupControls(),this.hasAttribute("poster")&&this._setupPoster(),this.hasAttribute("audio-src")&&this._setupParentAudioFromUrl(this.getAttribute("audio-src")),this.hasAttribute("src")&&(this.iframe.src=this.getAttribute("src"))}disconnectedCallback(){this.resizeObserver.disconnect(),window.removeEventListener("message",this._onMessage),this.iframe.removeEventListener("load",this._onIframeLoad),this._probeInterval&&clearInterval(this._probeInterval),this.controlsApi?.destroy();for(let e of this._parentMedia)e.el.pause(),e.el.src="";this._parentMedia=[]}attributeChangedCallback(e,t,s){switch(e){case"src":s&&(this._ready=!1,this.iframe.src=s);break;case"width":this._compositionWidth=parseInt(s||"1920",10),this._updateScale();break;case"height":this._compositionHeight=parseInt(s||"1080",10),this._updateScale();break;case"controls":s!==null?this._setupControls():(this.controlsApi?.destroy(),this.controlsApi=null);break;case"poster":this._setupPoster();break;case"playback-rate":{let i=parseFloat(s||"1");for(let a of this._parentMedia)a.el.playbackRate=i;this._sendControl("set-playback-rate",{playbackRate:i}),this.controlsApi?.updateSpeed(i),this.dispatchEvent(new Event("ratechange"));break}case"muted":for(let i of this._parentMedia)i.el.muted=s!==null;this._sendControl("set-muted",{muted:s!==null});break;case"audio-src":s&&this._setupParentAudioFromUrl(s);break}}get iframeElement(){return this.iframe}play(){this._hidePoster(),this._playParentMedia(),this._sendControl("play"),this._paused=!1,this.controlsApi?.updatePlaying(!0),this.dispatchEvent(new Event("play"))}pause(){this._pauseParentMedia(),this._sendControl("pause"),this._paused=!0,this.controlsApi?.updatePlaying(!1),this.dispatchEvent(new Event("pause"))}seek(e){let t=Math.round(e*A);this._sendControl("seek",{frame:t}),this._currentTime=e;for(let s of this._parentMedia){let i=e-s.start;i>=0&&i<s.duration&&(s.el.currentTime=i)}this._paused=!0,this.controlsApi?.updatePlaying(!1),this.controlsApi?.updateTime(this._currentTime,this._duration)}get currentTime(){return this._currentTime}set currentTime(e){this.seek(e)}get duration(){return this._duration}get paused(){return this._paused}get ready(){return this._ready}get playbackRate(){return parseFloat(this.getAttribute("playback-rate")||"1")}set playbackRate(e){this.setAttribute("playback-rate",String(e))}get muted(){return this.hasAttribute("muted")}set muted(e){e?this.setAttribute("muted",""):this.removeAttribute("muted")}get loop(){return this.hasAttribute("loop")}set loop(e){e?this.setAttribute("loop",""):this.removeAttribute("loop")}_sendControl(e,t={}){try{this.iframe.contentWindow?.postMessage({source:"hf-parent",type:"control",action:e,...t},"*")}catch{}}_isControlsClick(e){return e.composedPath().some(t=>t instanceof HTMLElement&&t.classList.contains("hfp-controls"))}_onMessage(e){if(e.source!==this.iframe.contentWindow)return;let t=e.data;if(!(!t||t.source!=="hf-preview")){if(t.type==="state"){this._currentTime=(t.frame??0)/A;let s=!this._paused;this._paused=!t.isPlaying,s&&this._paused?this._pauseParentMedia():!s&&!this._paused&&this._playParentMedia();let i=performance.now();(i-this._lastUpdateMs>100||this._paused!==s)&&(this._lastUpdateMs=i,this.controlsApi?.updateTime(this._currentTime,this._duration),this.controlsApi?.updatePlaying(!this._paused),this.dispatchEvent(new CustomEvent("timeupdate",{detail:{currentTime:this._currentTime}}))),this._currentTime>=this._duration&&!this._paused&&(this._pauseParentMedia(),this.loop?(this.seek(0),this.play()):(this._paused=!0,this.controlsApi?.updatePlaying(!1),this.dispatchEvent(new Event("ended"))))}t.type==="timeline"&&t.durationInFrames>0&&Number.isFinite(t.durationInFrames)&&(this._duration=t.durationInFrames/A,this.controlsApi?.updateTime(this._currentTime,this._duration)),t.type==="stage-size"&&t.width>0&&t.height>0&&(this._compositionWidth=t.width,this._compositionHeight=t.height,this._updateScale())}}_runtimeInjected=!1;_onIframeLoad(){let e=0;this._runtimeInjected=!1,this._probeInterval&&clearInterval(this._probeInterval),this._probeInterval=setInterval(()=>{e++;try{let t=this.iframe.contentWindow;if(!t)return;let s=!!(t.__hf||t.__player),i=!!(t.__timelines&&Object.keys(t.__timelines).length>0);if(!s&&i&&!this._runtimeInjected&&e>=5){this._injectRuntime();return}if(this._runtimeInjected&&!s)return;let d=(()=>{if(t.__player&&typeof t.__player.getDuration=="function")return t.__player;if(t.__timelines){let l=Object.keys(t.__timelines);if(l.length>0){let c=this.iframe.contentDocument?.querySelector("[data-composition-id]")?.getAttribute("data-composition-id"),h=c&&c in t.__timelines?c:l[l.length-1],p=t.__timelines[h];return{getDuration:()=>p.duration()}}}return null})();if(d&&d.getDuration()>0){clearInterval(this._probeInterval),this._duration=d.getDuration(),this._ready=!0,this.controlsApi?.updateTime(0,this._duration),this.dispatchEvent(new CustomEvent("ready",{detail:{duration:this._duration}}));let c=this.iframe.contentDocument?.querySelector("[data-composition-id]");if(c){let h=parseInt(c.getAttribute("data-width")||"0",10),p=parseInt(c.getAttribute("data-height")||"0",10);h>0&&p>0&&(this._compositionWidth=h,this._compositionHeight=p,this._updateScale())}this._setupParentMedia(),this.hasAttribute("autoplay")&&this.play();return}}catch{}e>=40&&(clearInterval(this._probeInterval),this.dispatchEvent(new CustomEvent("error",{detail:{message:"Composition timeline not found after 8s"}})))},200)}_injectRuntime(){this._runtimeInjected=!0;try{let e=this.iframe.contentDocument;if(!e)return;let t=e.createElement("script");t.src=Y,t.onload=()=>{},t.onerror=()=>{},(e.head||e.documentElement).appendChild(t)}catch{}}_updateScale(){let e=this.getBoundingClientRect();if(e.width===0||e.height===0)return;let t=Math.min(e.width/this._compositionWidth,e.height/this._compositionHeight);this.iframe.style.width=`${this._compositionWidth}px`,this.iframe.style.height=`${this._compositionHeight}px`,this.iframe.style.transform=`translate(-50%, -50%) scale(${t})`}_setupControls(){if(this.controlsApi)return;let e={onPlay:()=>this.play(),onPause:()=>this.pause(),onSeek:i=>this.seek(i*this._duration),onSpeedChange:i=>{this.playbackRate=i}},t=this.getAttribute("speed-presets"),s=t?t.split(",").map(Number).filter(i=>!isNaN(i)&&i>0):void 0;this.controlsApi=D(this.shadow,e,{speedPresets:s})}_setupPoster(){let e=this.getAttribute("poster");if(!e){this.posterEl?.remove(),this.posterEl=null;return}this.posterEl||(this.posterEl=document.createElement("img"),this.posterEl.className="hfp-poster",this.shadow.appendChild(this.posterEl)),this.posterEl.src=e}_playParentMedia(){for(let e of this._parentMedia)e.el.src&&e.el.play().then(()=>{this._muteIframeMedia()}).catch(()=>{})}_muteIframeMedia(){try{let e=this.iframe.contentDocument;if(!e)return;let t=e.querySelectorAll("audio[data-start], video[data-start]");for(let s of t)s.volume=0}catch{}}_pauseParentMedia(){for(let e of this._parentMedia)e.el.pause()}_createParentMedia(e,t,s,i){if(this._parentMedia.some(d=>d.el.src===e))return;let a=t==="video"?document.createElement("video"):new Audio;a.preload="auto",a.src=e,a.load(),a.muted=this.muted,this.playbackRate!==1&&(a.playbackRate=this.playbackRate),this._parentMedia.push({el:a,start:s,duration:i})}_setupParentAudioFromUrl(e){this._createParentMedia(e,"audio",0,1/0)}_setupParentMedia(){try{let e=this.iframe.contentDocument;if(!e)return;let t=e.querySelectorAll("audio[data-start], video[data-start]");for(let s of t){let i=s.getAttribute("src")||s.querySelector("source")?.src;if(!i)continue;let a=parseFloat(s.getAttribute("data-start")||"0"),d=parseFloat(s.getAttribute("data-duration")||"Infinity"),l=s.tagName==="VIDEO"?"video":"audio";this._createParentMedia(i,l,a,d)}}catch{}}_hidePoster(){this.posterEl?.remove(),this.posterEl=null}};customElements.get("hyperframes-player")||customElements.define("hyperframes-player",x);0&&(module.exports={HyperframesPlayer,SPEED_PRESETS,formatSpeed,formatTime});
|
|
199
199
|
//# sourceMappingURL=hyperframes-player.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hyperframes-player.ts","../src/styles.ts","../src/controls.ts"],"sourcesContent":["import { createControls, SPEED_PRESETS, type ControlsCallbacks } from \"./controls.js\";\nimport { PLAYER_STYLES } from \"./styles.js\";\n\nconst DEFAULT_FPS = 30;\nconst RUNTIME_CDN_URL =\n \"https://cdn.jsdelivr.net/npm/@hyperframes/core/dist/hyperframe.runtime.iife.js\";\n\nclass HyperframesPlayer extends HTMLElement {\n static get observedAttributes() {\n return [\"src\", \"width\", \"height\", \"controls\", \"muted\", \"poster\", \"playback-rate\", \"audio-src\"];\n }\n\n private shadow: ShadowRoot;\n private container: HTMLDivElement;\n private iframe: HTMLIFrameElement;\n private posterEl: HTMLImageElement | null = null;\n private controlsApi: ReturnType<typeof createControls> | null = null;\n private resizeObserver: ResizeObserver;\n\n private _ready = false;\n private _duration = 0;\n private _currentTime = 0;\n private _paused = true;\n private _compositionWidth = 1920;\n private _compositionHeight = 1080;\n private _probeInterval: ReturnType<typeof setInterval> | null = null;\n private _lastUpdateMs = 0;\n\n /**\n * Parent-frame media elements for mobile playback.\n *\n * Mobile browsers block media.play() inside iframes when the user gesture\n * happened in the parent frame — postMessage doesn't transfer user activation\n * (per the User Activation v2 spec). We extract ALL media sources from the\n * iframe's timed elements (audio/video with data-start), play them in the\n * parent frame (where the gesture lives), and disable the iframe copies.\n */\n private _parentMedia: Array<{\n el: HTMLMediaElement;\n start: number;\n duration: number;\n }> = [];\n\n constructor() {\n super();\n this.shadow = this.attachShadow({ mode: \"open\" });\n\n const style = document.createElement(\"style\");\n style.textContent = PLAYER_STYLES;\n this.shadow.appendChild(style);\n\n this.container = document.createElement(\"div\");\n this.container.className = \"hfp-container\";\n\n this.iframe = document.createElement(\"iframe\");\n this.iframe.className = \"hfp-iframe\";\n this.iframe.sandbox.add(\"allow-scripts\", \"allow-same-origin\");\n this.iframe.allow = \"autoplay; fullscreen\";\n this.iframe.referrerPolicy = \"no-referrer\";\n this.iframe.title = \"HyperFrames Composition\";\n\n this.container.appendChild(this.iframe);\n this.shadow.appendChild(this.container);\n\n // Clicking the bare player surface toggles play/pause.\n // Ignore shadow-DOM control interactions so overlay clicks don't double-handle.\n this.addEventListener(\"click\", (event) => {\n if (this._isControlsClick(event)) return;\n if (this._paused) this.play();\n else this.pause();\n });\n\n this.resizeObserver = new ResizeObserver(() => this._updateScale());\n\n this._onMessage = this._onMessage.bind(this);\n this._onIframeLoad = this._onIframeLoad.bind(this);\n }\n\n connectedCallback() {\n this.resizeObserver.observe(this);\n window.addEventListener(\"message\", this._onMessage);\n this.iframe.addEventListener(\"load\", this._onIframeLoad);\n\n if (this.hasAttribute(\"controls\")) this._setupControls();\n if (this.hasAttribute(\"poster\")) this._setupPoster();\n if (this.hasAttribute(\"audio-src\"))\n this._setupParentAudioFromUrl(this.getAttribute(\"audio-src\")!);\n if (this.hasAttribute(\"src\")) this.iframe.src = this.getAttribute(\"src\")!;\n }\n\n disconnectedCallback() {\n this.resizeObserver.disconnect();\n window.removeEventListener(\"message\", this._onMessage);\n this.iframe.removeEventListener(\"load\", this._onIframeLoad);\n if (this._probeInterval) clearInterval(this._probeInterval);\n this.controlsApi?.destroy();\n for (const m of this._parentMedia) {\n m.el.pause();\n m.el.src = \"\";\n }\n this._parentMedia = [];\n }\n\n attributeChangedCallback(name: string, _old: string | null, val: string | null) {\n switch (name) {\n case \"src\":\n if (val) {\n this._ready = false;\n this.iframe.src = val;\n }\n break;\n case \"width\":\n this._compositionWidth = parseInt(val || \"1920\", 10);\n this._updateScale();\n break;\n case \"height\":\n this._compositionHeight = parseInt(val || \"1080\", 10);\n this._updateScale();\n break;\n case \"controls\":\n if (val !== null) this._setupControls();\n else {\n this.controlsApi?.destroy();\n this.controlsApi = null;\n }\n break;\n case \"poster\":\n this._setupPoster();\n break;\n case \"playback-rate\": {\n const rate = parseFloat(val || \"1\");\n for (const m of this._parentMedia) m.el.playbackRate = rate;\n this._sendControl(\"set-playback-rate\", { playbackRate: rate });\n this.controlsApi?.updateSpeed(rate);\n this.dispatchEvent(new Event(\"ratechange\"));\n break;\n }\n case \"muted\":\n for (const m of this._parentMedia) m.el.muted = val !== null;\n this._sendControl(\"set-muted\", { muted: val !== null });\n break;\n case \"audio-src\":\n if (val) this._setupParentAudioFromUrl(val);\n break;\n }\n }\n\n // ── Public API ──\n\n /**\n * Access the inner `<iframe>` element rendering the composition.\n *\n * Use this when integrating the player with editors, recorders, or\n * timeline tools (e.g. `@hyperframes/studio`) that need to inspect\n * the composition's DOM or read its `__player` / `__timelines`\n * runtime objects.\n *\n * **Common pitfall:** the iframe lives inside the player's Shadow DOM.\n * Passing the `<hyperframes-player>` element itself to code that expects\n * an `<iframe>` will silently break — `.contentWindow` returns `null`.\n * Always extract `iframeElement` first:\n *\n * ```ts\n * // ❌ Wrong — element ref doesn't expose contentWindow\n * iframeRef.current = playerRef.current;\n *\n * // ✓ Right — bridge the actual iframe\n * iframeRef.current = playerRef.current.iframeElement;\n * ```\n */\n get iframeElement(): HTMLIFrameElement {\n return this.iframe;\n }\n\n play() {\n this._hidePoster();\n this._playParentMedia();\n this._sendControl(\"play\");\n this._paused = false;\n this.controlsApi?.updatePlaying(true);\n this.dispatchEvent(new Event(\"play\"));\n }\n\n pause() {\n this._pauseParentMedia();\n this._sendControl(\"pause\");\n this._paused = true;\n this.controlsApi?.updatePlaying(false);\n this.dispatchEvent(new Event(\"pause\"));\n }\n\n seek(timeInSeconds: number) {\n const frame = Math.round(timeInSeconds * DEFAULT_FPS);\n this._sendControl(\"seek\", { frame });\n this._currentTime = timeInSeconds;\n\n // Sync parent media positions (accounting for each element's start offset)\n for (const m of this._parentMedia) {\n const relTime = timeInSeconds - m.start;\n if (relTime >= 0 && relTime < m.duration) {\n m.el.currentTime = relTime;\n }\n }\n\n this._paused = true;\n this.controlsApi?.updatePlaying(false);\n this.controlsApi?.updateTime(this._currentTime, this._duration);\n }\n\n get currentTime() {\n return this._currentTime;\n }\n set currentTime(t: number) {\n this.seek(t);\n }\n\n get duration() {\n return this._duration;\n }\n get paused() {\n return this._paused;\n }\n get ready() {\n return this._ready;\n }\n\n get playbackRate() {\n return parseFloat(this.getAttribute(\"playback-rate\") || \"1\");\n }\n set playbackRate(r: number) {\n this.setAttribute(\"playback-rate\", String(r));\n }\n\n get muted() {\n return this.hasAttribute(\"muted\");\n }\n set muted(m: boolean) {\n if (m) this.setAttribute(\"muted\", \"\");\n else this.removeAttribute(\"muted\");\n }\n\n get loop() {\n return this.hasAttribute(\"loop\");\n }\n set loop(l: boolean) {\n if (l) this.setAttribute(\"loop\", \"\");\n else this.removeAttribute(\"loop\");\n }\n\n // ── Private ──\n\n private _sendControl(action: string, extra: Record<string, unknown> = {}) {\n try {\n this.iframe.contentWindow?.postMessage(\n { source: \"hf-parent\", type: \"control\", action, ...extra },\n \"*\",\n );\n } catch {\n /* cross-origin */\n }\n }\n\n private _isControlsClick(event: Event) {\n return event\n .composedPath()\n .some((target) => target instanceof HTMLElement && target.classList.contains(\"hfp-controls\"));\n }\n\n private _onMessage(e: MessageEvent) {\n if (e.source !== this.iframe.contentWindow) return;\n const data = e.data;\n if (!data || data.source !== \"hf-preview\") return;\n\n if (data.type === \"state\") {\n this._currentTime = (data.frame ?? 0) / DEFAULT_FPS;\n const wasPlaying = !this._paused;\n this._paused = !data.isPlaying;\n\n // Sync parent media on runtime play/pause transitions (e.g. browser\n // throttling, visibility change, or scrubber interaction in the iframe).\n if (wasPlaying && this._paused) {\n this._pauseParentMedia();\n } else if (!wasPlaying && !this._paused) {\n this._playParentMedia();\n }\n\n // Throttle UI updates and event dispatch to ~10fps to avoid excessive re-renders\n const now = performance.now();\n if (now - this._lastUpdateMs > 100 || this._paused !== wasPlaying) {\n this._lastUpdateMs = now;\n this.controlsApi?.updateTime(this._currentTime, this._duration);\n this.controlsApi?.updatePlaying(!this._paused);\n this.dispatchEvent(\n new CustomEvent(\"timeupdate\", { detail: { currentTime: this._currentTime } }),\n );\n }\n\n if (this._currentTime >= this._duration && !this._paused) {\n this._pauseParentMedia();\n if (this.loop) {\n this.seek(0);\n this.play();\n } else {\n this._paused = true;\n this.controlsApi?.updatePlaying(false);\n this.dispatchEvent(new Event(\"ended\"));\n }\n }\n }\n\n if (data.type === \"timeline\" && data.durationInFrames > 0) {\n // Ignore Infinity duration from runtime (caused by loop-inflated timelines without data-duration)\n // The player already has duration from the initial probe, so keep that.\n if (Number.isFinite(data.durationInFrames)) {\n this._duration = data.durationInFrames / DEFAULT_FPS;\n this.controlsApi?.updateTime(this._currentTime, this._duration);\n }\n }\n\n if (data.type === \"stage-size\" && data.width > 0 && data.height > 0) {\n this._compositionWidth = data.width;\n this._compositionHeight = data.height;\n this._updateScale();\n }\n }\n\n private _runtimeInjected = false;\n\n private _onIframeLoad() {\n let attempts = 0;\n this._runtimeInjected = false;\n if (this._probeInterval) clearInterval(this._probeInterval);\n\n this._probeInterval = setInterval(() => {\n attempts++;\n try {\n const win = this.iframe.contentWindow as Window & {\n __player?: { getDuration: () => number };\n __timelines?: Record<string, { duration: () => number }>;\n __hf?: unknown;\n };\n if (!win) return;\n\n // Check if the runtime bridge is active (__hf or __player from the runtime)\n const hasRuntime = !!(win.__hf || win.__player);\n const hasTimelines = !!(win.__timelines && Object.keys(win.__timelines).length > 0);\n\n // Auto-inject runtime if GSAP timelines exist but no runtime bridge\n if (!hasRuntime && hasTimelines && !this._runtimeInjected && attempts >= 5) {\n this._injectRuntime();\n return; // Wait for runtime to load and initialize\n }\n\n // Runtime was injected but hasn't loaded yet — keep waiting\n if (this._runtimeInjected && !hasRuntime) {\n return;\n }\n\n const getAdapter = () => {\n if (win.__player && typeof win.__player.getDuration === \"function\") return win.__player;\n if (win.__timelines) {\n const keys = Object.keys(win.__timelines);\n if (keys.length > 0) {\n // Resolve the root composition id from the DOM — the outermost\n // `[data-composition-id]` element is the master. Bundled previews\n // register the root composition alongside sub-compositions, and\n // without this lookup Object.keys() order would make a\n // sub-composition's duration hijack the overall video length.\n const rootId = this.iframe.contentDocument\n ?.querySelector(\"[data-composition-id]\")\n ?.getAttribute(\"data-composition-id\");\n const key = rootId && rootId in win.__timelines ? rootId : keys[keys.length - 1];\n const tl = win.__timelines[key];\n return { getDuration: () => tl.duration() };\n }\n }\n return null;\n };\n\n const adapter = getAdapter();\n if (adapter && adapter.getDuration() > 0) {\n clearInterval(this._probeInterval!);\n this._duration = adapter.getDuration();\n this._ready = true;\n this.controlsApi?.updateTime(0, this._duration);\n this.dispatchEvent(new CustomEvent(\"ready\", { detail: { duration: this._duration } }));\n\n // Auto-detect dimensions from composition\n const doc = this.iframe.contentDocument;\n const root = doc?.querySelector(\"[data-composition-id]\");\n if (root) {\n const w = parseInt(root.getAttribute(\"data-width\") || \"0\", 10);\n const h = parseInt(root.getAttribute(\"data-height\") || \"0\", 10);\n if (w > 0 && h > 0) {\n this._compositionWidth = w;\n this._compositionHeight = h;\n this._updateScale();\n }\n }\n\n this._setupParentMedia();\n\n if (this.hasAttribute(\"autoplay\")) {\n this.play();\n }\n return;\n }\n } catch {\n /* cross-origin */\n }\n\n if (attempts >= 40) {\n clearInterval(this._probeInterval!);\n this.dispatchEvent(\n new CustomEvent(\"error\", {\n detail: { message: \"Composition timeline not found after 8s\" },\n }),\n );\n }\n }, 200);\n }\n\n /** Inject the HyperFrames runtime into the iframe if not already present. */\n private _injectRuntime() {\n this._runtimeInjected = true;\n try {\n const doc = this.iframe.contentDocument;\n if (!doc) return;\n const script = doc.createElement(\"script\");\n script.src = RUNTIME_CDN_URL;\n script.onload = () => {\n // Runtime loaded — the probe interval will pick up __hf on next tick\n };\n script.onerror = () => {\n // CDN failed — the probe will continue and eventually timeout\n };\n (doc.head || doc.documentElement).appendChild(script);\n } catch {\n /* cross-origin — can't inject */\n }\n }\n\n private _updateScale() {\n const rect = this.getBoundingClientRect();\n if (rect.width === 0 || rect.height === 0) return;\n const scale = Math.min(\n rect.width / this._compositionWidth,\n rect.height / this._compositionHeight,\n );\n this.iframe.style.width = `${this._compositionWidth}px`;\n this.iframe.style.height = `${this._compositionHeight}px`;\n this.iframe.style.transform = `translate(-50%, -50%) scale(${scale})`;\n }\n\n private _setupControls() {\n if (this.controlsApi) return;\n const callbacks: ControlsCallbacks = {\n onPlay: () => this.play(),\n onPause: () => this.pause(),\n onSeek: (fraction) => this.seek(fraction * this._duration),\n onSpeedChange: (speed) => {\n this.playbackRate = speed;\n },\n };\n const presetsAttr = this.getAttribute(\"speed-presets\");\n const speedPresets = presetsAttr\n ? presetsAttr\n .split(\",\")\n .map(Number)\n .filter((n) => !isNaN(n) && n > 0)\n : undefined;\n this.controlsApi = createControls(this.shadow, callbacks, { speedPresets });\n }\n\n private _setupPoster() {\n const url = this.getAttribute(\"poster\");\n if (!url) {\n this.posterEl?.remove();\n this.posterEl = null;\n return;\n }\n if (!this.posterEl) {\n this.posterEl = document.createElement(\"img\");\n this.posterEl.className = \"hfp-poster\";\n this.shadow.appendChild(this.posterEl);\n }\n this.posterEl.src = url;\n }\n\n private _playParentMedia() {\n for (const m of this._parentMedia) {\n if (m.el.src) m.el.play().catch(() => {});\n }\n }\n\n private _pauseParentMedia() {\n for (const m of this._parentMedia) m.el.pause();\n }\n\n /** Create a parent-frame media element, configure it, and start preloading. */\n private _createParentMedia(src: string, tag: \"audio\" | \"video\", start: number, duration: number) {\n // Deduplicate — browsers normalize URLs so we compare on the element after assignment\n if (this._parentMedia.some((m) => m.el.src === src)) return;\n\n const el = tag === \"video\" ? document.createElement(\"video\") : new Audio();\n el.preload = \"auto\";\n el.src = src;\n el.load();\n el.muted = this.muted;\n if (this.playbackRate !== 1) el.playbackRate = this.playbackRate;\n\n this._parentMedia.push({ el, start, duration });\n }\n\n /**\n * Set up a single parent-frame audio from an explicit URL (via `audio-src`).\n * Convenience for the common single-narration case — starts preloading\n * immediately without waiting for the iframe to load.\n */\n private _setupParentAudioFromUrl(audioSrc: string) {\n this._createParentMedia(audioSrc, \"audio\", 0, Infinity);\n }\n\n /**\n * Extract ALL timed media (audio/video with data-start) from the iframe's\n * DOM and create parent-frame copies. Disables the iframe originals so the\n * runtime doesn't try to play them (which would fail on mobile and cause\n * double playback on desktop).\n *\n * If `audio-src` was already set, this just disables the iframe media.\n */\n private _setupParentMedia() {\n try {\n const doc = this.iframe.contentDocument;\n if (!doc) return;\n\n // Find all timed media — matches the runtime's media.ts selector\n const mediaEls = doc.querySelectorAll<HTMLMediaElement>(\n \"audio[data-start], video[data-start]\",\n );\n\n for (const iframeEl of mediaEls) {\n const src = iframeEl.getAttribute(\"src\") || iframeEl.querySelector(\"source\")?.src;\n if (!src) continue;\n\n const start = parseFloat(iframeEl.getAttribute(\"data-start\") || \"0\");\n const duration = parseFloat(iframeEl.getAttribute(\"data-duration\") || \"Infinity\");\n const tag = iframeEl.tagName === \"VIDEO\" ? (\"video\" as const) : (\"audio\" as const);\n\n this._createParentMedia(src, tag, start, duration);\n\n // Disable the iframe element so the runtime ignores it\n iframeEl.removeAttribute(\"src\");\n iframeEl.removeAttribute(\"data-start\");\n iframeEl.removeAttribute(\"data-duration\");\n iframeEl.querySelectorAll(\"source\").forEach((s) => s.remove());\n }\n } catch {\n // Cross-origin iframe — can't access DOM, fall back to iframe media\n }\n }\n\n private _hidePoster() {\n this.posterEl?.remove();\n this.posterEl = null;\n }\n}\n\nif (!customElements.get(\"hyperframes-player\")) {\n customElements.define(\"hyperframes-player\", HyperframesPlayer);\n}\n\nexport { HyperframesPlayer };\nexport { formatTime, formatSpeed, SPEED_PRESETS } from \"./controls.js\";\nexport type { ControlsCallbacks, ControlsOptions } from \"./controls.js\";\n","export const PLAYER_STYLES = /* css */ `\n :host {\n display: block;\n position: relative;\n overflow: hidden;\n background: #000;\n contain: layout style;\n }\n\n .hfp-container {\n position: absolute;\n inset: 0;\n overflow: hidden;\n pointer-events: none;\n }\n\n\n .hfp-iframe {\n position: absolute;\n top: 50%;\n left: 50%;\n border: none;\n pointer-events: none;\n }\n\n .hfp-poster {\n position: absolute;\n inset: 0;\n object-fit: contain;\n z-index: 1;\n pointer-events: none;\n }\n\n /* ── Theming via CSS custom properties ──\n *\n * Override from outside the shadow DOM:\n * hyperframes-player {\n * --hfp-controls-bg: linear-gradient(transparent, rgba(0,0,0,0.9));\n * --hfp-accent: #ff6b6b;\n * --hfp-font: \"Inter\", sans-serif;\n * }\n */\n\n .hfp-controls {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n display: flex;\n align-items: center;\n gap: var(--hfp-controls-gap, 12px);\n padding: var(--hfp-controls-padding, 8px 16px);\n background: var(--hfp-controls-bg, linear-gradient(transparent, rgba(0, 0, 0, 0.7)));\n color: var(--hfp-color, #fff);\n font-family: var(--hfp-font, system-ui, -apple-system, sans-serif);\n font-size: var(--hfp-font-size, 13px);\n z-index: 10;\n pointer-events: auto;\n opacity: 1;\n transition: opacity 0.3s ease;\n user-select: none;\n }\n\n .hfp-controls.hfp-hidden {\n opacity: 0;\n pointer-events: none;\n }\n\n .hfp-play-btn {\n background: none;\n border: none;\n color: var(--hfp-color, #fff);\n cursor: pointer;\n padding: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n flex-shrink: 0;\n z-index: 10;\n }\n\n .hfp-play-btn:hover {\n opacity: 0.8;\n }\n\n .hfp-play-btn svg,\n .hfp-play-btn svg * {\n pointer-events: none;\n }\n\n .hfp-scrubber {\n flex: 1;\n height: var(--hfp-scrubber-height, 4px);\n background: var(--hfp-scrubber-bg, rgba(255, 255, 255, 0.3));\n border-radius: var(--hfp-scrubber-radius, 2px);\n cursor: pointer;\n position: relative;\n }\n\n .hfp-scrubber:hover {\n height: var(--hfp-scrubber-height-hover, 6px);\n }\n\n .hfp-progress {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n background: var(--hfp-accent, #fff);\n border-radius: var(--hfp-scrubber-radius, 2px);\n pointer-events: none;\n }\n\n .hfp-time {\n flex-shrink: 0;\n font-variant-numeric: tabular-nums;\n opacity: 0.9;\n }\n\n .hfp-speed-wrap {\n position: relative;\n flex-shrink: 0;\n }\n\n .hfp-speed-btn {\n background: var(--hfp-speed-btn-bg, rgba(255, 255, 255, 0.15));\n border: none;\n border-radius: var(--hfp-speed-btn-radius, 4px);\n color: var(--hfp-color, #fff);\n cursor: pointer;\n font-family: var(--hfp-font, system-ui, -apple-system, sans-serif);\n font-size: 12px;\n font-variant-numeric: tabular-nums;\n font-weight: 600;\n padding: 4px 8px;\n min-width: 40px;\n text-align: center;\n transition: background 0.15s ease;\n }\n\n .hfp-speed-btn:hover {\n background: var(--hfp-speed-btn-bg-hover, rgba(255, 255, 255, 0.3));\n }\n\n .hfp-speed-menu {\n position: absolute;\n bottom: calc(100% + 8px);\n right: 0;\n background: var(--hfp-menu-bg, rgba(20, 20, 20, 0.95));\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n border: 1px solid var(--hfp-menu-border, rgba(255, 255, 255, 0.1));\n border-radius: var(--hfp-menu-radius, 8px);\n padding: 4px;\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 80px;\n opacity: 0;\n visibility: hidden;\n transform: translateY(4px);\n transition: opacity 0.15s ease, transform 0.15s ease, visibility 0.15s;\n box-shadow: var(--hfp-menu-shadow, 0 8px 24px rgba(0, 0, 0, 0.4));\n }\n\n .hfp-speed-menu.hfp-open {\n opacity: 1;\n visibility: visible;\n transform: translateY(0);\n }\n\n .hfp-speed-option {\n background: none;\n border: none;\n border-radius: 4px;\n color: var(--hfp-menu-color, rgba(255, 255, 255, 0.7));\n cursor: pointer;\n font-family: var(--hfp-font, system-ui, -apple-system, sans-serif);\n font-size: 13px;\n font-variant-numeric: tabular-nums;\n padding: 6px 12px;\n text-align: left;\n transition: background 0.1s ease, color 0.1s ease;\n white-space: nowrap;\n }\n\n .hfp-speed-option:hover {\n background: var(--hfp-menu-hover-bg, rgba(255, 255, 255, 0.1));\n color: var(--hfp-color, #fff);\n }\n\n .hfp-speed-option.hfp-active {\n color: var(--hfp-accent, #fff);\n font-weight: 600;\n }\n`;\n\nexport const PLAY_ICON = `<svg width=\"24\" height=\"24\" viewBox=\"0 0 18 18\" fill=\"currentColor\"><polygon points=\"4,2 16,9 4,16\"/></svg>`;\nexport const PAUSE_ICON = `<svg width=\"24\" height=\"24\" viewBox=\"0 0 18 18\" fill=\"currentColor\"><rect x=\"3\" y=\"2\" width=\"4\" height=\"14\"/><rect x=\"11\" y=\"2\" width=\"4\" height=\"14\"/></svg>`;\n","import { PLAY_ICON, PAUSE_ICON } from \"./styles.js\";\n\nexport interface ControlsCallbacks {\n onPlay: () => void;\n onPause: () => void;\n onSeek: (fraction: number) => void;\n onSpeedChange: (speed: number) => void;\n}\n\n/** Default logarithmic speed presets — each step roughly doubles/halves. */\nexport const SPEED_PRESETS = [0.25, 0.5, 1, 1.5, 2, 4] as const;\n\nexport interface ControlsOptions {\n /** Speed presets shown in the menu. Defaults to SPEED_PRESETS. */\n speedPresets?: readonly number[];\n}\n\nexport function formatSpeed(speed: number): string {\n return Number.isInteger(speed) ? `${speed}x` : `${speed}x`;\n}\n\nexport function formatTime(seconds: number): string {\n // Handle non-finite values gracefully\n if (!Number.isFinite(seconds) || seconds < 0) {\n return \"0:00\";\n }\n const s = Math.floor(seconds);\n const m = Math.floor(s / 60);\n const sec = s % 60;\n return `${m}:${sec.toString().padStart(2, \"0\")}`;\n}\n\nexport function createControls(\n parent: ShadowRoot | HTMLElement,\n callbacks: ControlsCallbacks,\n options: ControlsOptions = {},\n): {\n updateTime: (current: number, duration: number) => void;\n updatePlaying: (playing: boolean) => void;\n updateSpeed: (speed: number) => void;\n show: () => void;\n hide: () => void;\n destroy: () => void;\n} {\n const presets = options.speedPresets ?? SPEED_PRESETS;\n\n const controls = document.createElement(\"div\");\n controls.className = \"hfp-controls\";\n // Keep overlay interactions from falling through to the host-level click toggle.\n controls.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n });\n\n const playBtn = document.createElement(\"button\");\n playBtn.className = \"hfp-play-btn\";\n playBtn.type = \"button\";\n playBtn.innerHTML = PLAY_ICON;\n playBtn.setAttribute(\"aria-label\", \"Play\");\n\n const scrubber = document.createElement(\"div\");\n scrubber.className = \"hfp-scrubber\";\n const progress = document.createElement(\"div\");\n progress.className = \"hfp-progress\";\n progress.style.width = \"0%\";\n scrubber.appendChild(progress);\n\n const time = document.createElement(\"span\");\n time.className = \"hfp-time\";\n time.textContent = \"0:00 / 0:00\";\n\n const speedWrap = document.createElement(\"div\");\n speedWrap.className = \"hfp-speed-wrap\";\n\n const speedBtn = document.createElement(\"button\");\n speedBtn.className = \"hfp-speed-btn\";\n speedBtn.type = \"button\";\n speedBtn.textContent = \"1x\";\n speedBtn.setAttribute(\"aria-label\", \"Playback speed\");\n\n const speedMenu = document.createElement(\"div\");\n speedMenu.className = \"hfp-speed-menu\";\n speedMenu.setAttribute(\"role\", \"menu\");\n for (const preset of presets) {\n const item = document.createElement(\"button\");\n item.className = \"hfp-speed-option\";\n item.type = \"button\";\n item.setAttribute(\"role\", \"menuitem\");\n item.dataset.speed = String(preset);\n item.textContent = formatSpeed(preset);\n if (preset === 1) item.classList.add(\"hfp-active\");\n speedMenu.appendChild(item);\n }\n\n speedWrap.appendChild(speedMenu);\n speedWrap.appendChild(speedBtn);\n\n controls.appendChild(playBtn);\n controls.appendChild(scrubber);\n controls.appendChild(time);\n controls.appendChild(speedWrap);\n parent.appendChild(controls);\n\n let isPlaying = false;\n let hideTimeout: ReturnType<typeof setTimeout> | null = null;\n let speedIndex = presets.indexOf(1); // start at 1x\n if (speedIndex === -1) speedIndex = 0;\n\n playBtn.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n if (isPlaying) callbacks.onPause();\n else callbacks.onPlay();\n });\n\n const setActiveOption = (speed: number) => {\n for (const opt of speedMenu.querySelectorAll(\".hfp-speed-option\")) {\n opt.classList.toggle(\"hfp-active\", (opt as HTMLElement).dataset.speed === String(speed));\n }\n };\n\n speedBtn.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n const isOpen = speedMenu.classList.toggle(\"hfp-open\");\n speedBtn.setAttribute(\"aria-expanded\", String(isOpen));\n });\n\n speedMenu.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n const target = (e.target as HTMLElement).closest(\".hfp-speed-option\") as HTMLElement | null;\n if (!target) return;\n const newSpeed = parseFloat(target.dataset.speed!);\n speedIndex = presets.indexOf(newSpeed);\n speedBtn.textContent = formatSpeed(newSpeed);\n setActiveOption(newSpeed);\n speedMenu.classList.remove(\"hfp-open\");\n speedBtn.setAttribute(\"aria-expanded\", \"false\");\n callbacks.onSpeedChange(newSpeed);\n });\n\n // Close menu when clicking outside\n const onDocClick = () => {\n speedMenu.classList.remove(\"hfp-open\");\n speedBtn.setAttribute(\"aria-expanded\", \"false\");\n };\n document.addEventListener(\"click\", onDocClick);\n\n const handleScrubAt = (clientX: number) => {\n const rect = scrubber.getBoundingClientRect();\n const fraction = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));\n callbacks.onSeek(fraction);\n };\n\n let scrubbing = false;\n\n scrubber.addEventListener(\"mousedown\", (e) => {\n e.stopPropagation();\n scrubbing = true;\n handleScrubAt(e.clientX);\n });\n const onMouseMove = (e: MouseEvent) => {\n if (scrubbing) handleScrubAt(e.clientX);\n };\n const onMouseUp = () => {\n scrubbing = false;\n };\n document.addEventListener(\"mousemove\", onMouseMove);\n document.addEventListener(\"mouseup\", onMouseUp);\n\n scrubber.addEventListener(\n \"touchstart\",\n (e) => {\n scrubbing = true;\n const touch = e.touches[0];\n if (touch) handleScrubAt(touch.clientX);\n },\n { passive: true },\n );\n const onTouchMove = (e: TouchEvent) => {\n if (scrubbing) {\n const touch = e.touches[0];\n if (touch) handleScrubAt(touch.clientX);\n }\n };\n const onTouchEnd = () => {\n scrubbing = false;\n };\n document.addEventListener(\"touchmove\", onTouchMove, { passive: true });\n document.addEventListener(\"touchend\", onTouchEnd);\n\n const startHideTimer = () => {\n if (hideTimeout) clearTimeout(hideTimeout);\n hideTimeout = setTimeout(() => {\n if (isPlaying) controls.classList.add(\"hfp-hidden\");\n }, 3000);\n };\n\n const host = parent instanceof ShadowRoot ? (parent.host as HTMLElement) : parent;\n host.addEventListener(\"mousemove\", () => {\n controls.classList.remove(\"hfp-hidden\");\n startHideTimer();\n });\n host.addEventListener(\"mouseleave\", () => {\n if (isPlaying) controls.classList.add(\"hfp-hidden\");\n });\n\n return {\n updateTime(current: number, duration: number) {\n const pct = duration > 0 ? (current / duration) * 100 : 0;\n progress.style.width = `${pct}%`;\n time.textContent = `${formatTime(current)} / ${formatTime(duration)}`;\n },\n updatePlaying(playing: boolean) {\n isPlaying = playing;\n playBtn.innerHTML = playing ? PAUSE_ICON : PLAY_ICON;\n playBtn.setAttribute(\"aria-label\", playing ? \"Pause\" : \"Play\");\n if (playing) startHideTimer();\n else controls.classList.remove(\"hfp-hidden\");\n },\n updateSpeed(speed: number) {\n const idx = presets.indexOf(speed);\n if (idx !== -1) speedIndex = idx;\n speedBtn.textContent = formatSpeed(speed);\n setActiveOption(speed);\n },\n show() {\n controls.style.display = \"\";\n },\n hide() {\n controls.style.display = \"none\";\n },\n destroy() {\n document.removeEventListener(\"mousemove\", onMouseMove);\n document.removeEventListener(\"mouseup\", onMouseUp);\n document.removeEventListener(\"touchmove\", onTouchMove);\n document.removeEventListener(\"touchend\", onTouchEnd);\n document.removeEventListener(\"click\", onDocClick);\n if (hideTimeout) clearTimeout(hideTimeout);\n },\n };\n}\n"],"mappings":"yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,uBAAAE,EAAA,kBAAAC,EAAA,gBAAAC,EAAA,eAAAC,IAAA,eAAAC,EAAAN,GCAO,IAAMO,EAA0B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAuM1BC,EAAY,8GACZC,EAAa,gKC9LnB,IAAMC,EAAgB,CAAC,IAAM,GAAK,EAAG,IAAK,EAAG,CAAC,EAO9C,SAASC,EAAYC,EAAuB,CACjD,OAAO,OAAO,UAAUA,CAAK,EAAI,GAAGA,CAAK,IAAM,GAAGA,CAAK,GACzD,CAEO,SAASC,EAAWC,EAAyB,CAElD,GAAI,CAAC,OAAO,SAASA,CAAO,GAAKA,EAAU,EACzC,MAAO,OAET,IAAMC,EAAI,KAAK,MAAMD,CAAO,EACtBE,EAAI,KAAK,MAAMD,EAAI,EAAE,EACrBE,EAAMF,EAAI,GAChB,MAAO,GAAGC,CAAC,IAAIC,EAAI,SAAS,EAAE,SAAS,EAAG,GAAG,CAAC,EAChD,CAEO,SAASC,EACdC,EACAC,EACAC,EAA2B,CAAC,EAQ5B,CACA,IAAMC,EAAUD,EAAQ,cAAgBX,EAElCa,EAAW,SAAS,cAAc,KAAK,EAC7CA,EAAS,UAAY,eAErBA,EAAS,iBAAiB,QAAUC,GAAM,CACxCA,EAAE,gBAAgB,CACpB,CAAC,EAED,IAAMC,EAAU,SAAS,cAAc,QAAQ,EAC/CA,EAAQ,UAAY,eACpBA,EAAQ,KAAO,SACfA,EAAQ,UAAYC,EACpBD,EAAQ,aAAa,aAAc,MAAM,EAEzC,IAAME,EAAW,SAAS,cAAc,KAAK,EAC7CA,EAAS,UAAY,eACrB,IAAMC,EAAW,SAAS,cAAc,KAAK,EAC7CA,EAAS,UAAY,eACrBA,EAAS,MAAM,MAAQ,KACvBD,EAAS,YAAYC,CAAQ,EAE7B,IAAMC,EAAO,SAAS,cAAc,MAAM,EAC1CA,EAAK,UAAY,WACjBA,EAAK,YAAc,cAEnB,IAAMC,EAAY,SAAS,cAAc,KAAK,EAC9CA,EAAU,UAAY,iBAEtB,IAAMC,EAAW,SAAS,cAAc,QAAQ,EAChDA,EAAS,UAAY,gBACrBA,EAAS,KAAO,SAChBA,EAAS,YAAc,KACvBA,EAAS,aAAa,aAAc,gBAAgB,EAEpD,IAAMC,EAAY,SAAS,cAAc,KAAK,EAC9CA,EAAU,UAAY,iBACtBA,EAAU,aAAa,OAAQ,MAAM,EACrC,QAAWC,KAAUX,EAAS,CAC5B,IAAMY,EAAO,SAAS,cAAc,QAAQ,EAC5CA,EAAK,UAAY,mBACjBA,EAAK,KAAO,SACZA,EAAK,aAAa,OAAQ,UAAU,EACpCA,EAAK,QAAQ,MAAQ,OAAOD,CAAM,EAClCC,EAAK,YAAcvB,EAAYsB,CAAM,EACjCA,IAAW,GAAGC,EAAK,UAAU,IAAI,YAAY,EACjDF,EAAU,YAAYE,CAAI,CAC5B,CAEAJ,EAAU,YAAYE,CAAS,EAC/BF,EAAU,YAAYC,CAAQ,EAE9BR,EAAS,YAAYE,CAAO,EAC5BF,EAAS,YAAYI,CAAQ,EAC7BJ,EAAS,YAAYM,CAAI,EACzBN,EAAS,YAAYO,CAAS,EAC9BX,EAAO,YAAYI,CAAQ,EAE3B,IAAIY,EAAY,GACZC,EAAoD,KACpDC,EAAaf,EAAQ,QAAQ,CAAC,EAC9Be,IAAe,KAAIA,EAAa,GAEpCZ,EAAQ,iBAAiB,QAAUD,GAAM,CACvCA,EAAE,gBAAgB,EACdW,EAAWf,EAAU,QAAQ,EAC5BA,EAAU,OAAO,CACxB,CAAC,EAED,IAAMkB,EAAmB1B,GAAkB,CACzC,QAAW2B,KAAOP,EAAU,iBAAiB,mBAAmB,EAC9DO,EAAI,UAAU,OAAO,aAAeA,EAAoB,QAAQ,QAAU,OAAO3B,CAAK,CAAC,CAE3F,EAEAmB,EAAS,iBAAiB,QAAUP,GAAM,CACxCA,EAAE,gBAAgB,EAClB,IAAMgB,EAASR,EAAU,UAAU,OAAO,UAAU,EACpDD,EAAS,aAAa,gBAAiB,OAAOS,CAAM,CAAC,CACvD,CAAC,EAEDR,EAAU,iBAAiB,QAAUR,GAAM,CACzCA,EAAE,gBAAgB,EAClB,IAAMiB,EAAUjB,EAAE,OAAuB,QAAQ,mBAAmB,EACpE,GAAI,CAACiB,EAAQ,OACb,IAAMC,EAAW,WAAWD,EAAO,QAAQ,KAAM,EACjDJ,EAAaf,EAAQ,QAAQoB,CAAQ,EACrCX,EAAS,YAAcpB,EAAY+B,CAAQ,EAC3CJ,EAAgBI,CAAQ,EACxBV,EAAU,UAAU,OAAO,UAAU,EACrCD,EAAS,aAAa,gBAAiB,OAAO,EAC9CX,EAAU,cAAcsB,CAAQ,CAClC,CAAC,EAGD,IAAMC,EAAa,IAAM,CACvBX,EAAU,UAAU,OAAO,UAAU,EACrCD,EAAS,aAAa,gBAAiB,OAAO,CAChD,EACA,SAAS,iBAAiB,QAASY,CAAU,EAE7C,IAAMC,EAAiBC,GAAoB,CACzC,IAAMC,EAAOnB,EAAS,sBAAsB,EACtCoB,EAAW,KAAK,IAAI,EAAG,KAAK,IAAI,GAAIF,EAAUC,EAAK,MAAQA,EAAK,KAAK,CAAC,EAC5E1B,EAAU,OAAO2B,CAAQ,CAC3B,EAEIC,EAAY,GAEhBrB,EAAS,iBAAiB,YAAcH,GAAM,CAC5CA,EAAE,gBAAgB,EAClBwB,EAAY,GACZJ,EAAcpB,EAAE,OAAO,CACzB,CAAC,EACD,IAAMyB,EAAezB,GAAkB,CACjCwB,GAAWJ,EAAcpB,EAAE,OAAO,CACxC,EACM0B,EAAY,IAAM,CACtBF,EAAY,EACd,EACA,SAAS,iBAAiB,YAAaC,CAAW,EAClD,SAAS,iBAAiB,UAAWC,CAAS,EAE9CvB,EAAS,iBACP,aACCH,GAAM,CACLwB,EAAY,GACZ,IAAMG,EAAQ3B,EAAE,QAAQ,CAAC,EACrB2B,GAAOP,EAAcO,EAAM,OAAO,CACxC,EACA,CAAE,QAAS,EAAK,CAClB,EACA,IAAMC,EAAe5B,GAAkB,CACrC,GAAIwB,EAAW,CACb,IAAMG,EAAQ3B,EAAE,QAAQ,CAAC,EACrB2B,GAAOP,EAAcO,EAAM,OAAO,CACxC,CACF,EACME,EAAa,IAAM,CACvBL,EAAY,EACd,EACA,SAAS,iBAAiB,YAAaI,EAAa,CAAE,QAAS,EAAK,CAAC,EACrE,SAAS,iBAAiB,WAAYC,CAAU,EAEhD,IAAMC,EAAiB,IAAM,CACvBlB,GAAa,aAAaA,CAAW,EACzCA,EAAc,WAAW,IAAM,CACzBD,GAAWZ,EAAS,UAAU,IAAI,YAAY,CACpD,EAAG,GAAI,CACT,EAEMgC,EAAOpC,aAAkB,WAAcA,EAAO,KAAuBA,EAC3E,OAAAoC,EAAK,iBAAiB,YAAa,IAAM,CACvChC,EAAS,UAAU,OAAO,YAAY,EACtC+B,EAAe,CACjB,CAAC,EACDC,EAAK,iBAAiB,aAAc,IAAM,CACpCpB,GAAWZ,EAAS,UAAU,IAAI,YAAY,CACpD,CAAC,EAEM,CACL,WAAWiC,EAAiBC,EAAkB,CAC5C,IAAMC,EAAMD,EAAW,EAAKD,EAAUC,EAAY,IAAM,EACxD7B,EAAS,MAAM,MAAQ,GAAG8B,CAAG,IAC7B7B,EAAK,YAAc,GAAGhB,EAAW2C,CAAO,CAAC,MAAM3C,EAAW4C,CAAQ,CAAC,EACrE,EACA,cAAcE,EAAkB,CAC9BxB,EAAYwB,EACZlC,EAAQ,UAAYkC,EAAUC,EAAalC,EAC3CD,EAAQ,aAAa,aAAckC,EAAU,QAAU,MAAM,EACzDA,EAASL,EAAe,EACvB/B,EAAS,UAAU,OAAO,YAAY,CAC7C,EACA,YAAYX,EAAe,CACzB,IAAMiD,EAAMvC,EAAQ,QAAQV,CAAK,EAC7BiD,IAAQ,KAAIxB,EAAawB,GAC7B9B,EAAS,YAAcpB,EAAYC,CAAK,EACxC0B,EAAgB1B,CAAK,CACvB,EACA,MAAO,CACLW,EAAS,MAAM,QAAU,EAC3B,EACA,MAAO,CACLA,EAAS,MAAM,QAAU,MAC3B,EACA,SAAU,CACR,SAAS,oBAAoB,YAAa0B,CAAW,EACrD,SAAS,oBAAoB,UAAWC,CAAS,EACjD,SAAS,oBAAoB,YAAaE,CAAW,EACrD,SAAS,oBAAoB,WAAYC,CAAU,EACnD,SAAS,oBAAoB,QAASV,CAAU,EAC5CP,GAAa,aAAaA,CAAW,CAC3C,CACF,CACF,CF3OA,IAAM0B,EAAc,GACdC,EACJ,iFAEIC,EAAN,cAAgC,WAAY,CAC1C,WAAW,oBAAqB,CAC9B,MAAO,CAAC,MAAO,QAAS,SAAU,WAAY,QAAS,SAAU,gBAAiB,WAAW,CAC/F,CAEQ,OACA,UACA,OACA,SAAoC,KACpC,YAAwD,KACxD,eAEA,OAAS,GACT,UAAY,EACZ,aAAe,EACf,QAAU,GACV,kBAAoB,KACpB,mBAAqB,KACrB,eAAwD,KACxD,cAAgB,EAWhB,aAIH,CAAC,EAEN,aAAc,CACZ,MAAM,EACN,KAAK,OAAS,KAAK,aAAa,CAAE,KAAM,MAAO,CAAC,EAEhD,IAAMC,EAAQ,SAAS,cAAc,OAAO,EAC5CA,EAAM,YAAcC,EACpB,KAAK,OAAO,YAAYD,CAAK,EAE7B,KAAK,UAAY,SAAS,cAAc,KAAK,EAC7C,KAAK,UAAU,UAAY,gBAE3B,KAAK,OAAS,SAAS,cAAc,QAAQ,EAC7C,KAAK,OAAO,UAAY,aACxB,KAAK,OAAO,QAAQ,IAAI,gBAAiB,mBAAmB,EAC5D,KAAK,OAAO,MAAQ,uBACpB,KAAK,OAAO,eAAiB,cAC7B,KAAK,OAAO,MAAQ,0BAEpB,KAAK,UAAU,YAAY,KAAK,MAAM,EACtC,KAAK,OAAO,YAAY,KAAK,SAAS,EAItC,KAAK,iBAAiB,QAAUE,GAAU,CACpC,KAAK,iBAAiBA,CAAK,IAC3B,KAAK,QAAS,KAAK,KAAK,EACvB,KAAK,MAAM,EAClB,CAAC,EAED,KAAK,eAAiB,IAAI,eAAe,IAAM,KAAK,aAAa,CAAC,EAElE,KAAK,WAAa,KAAK,WAAW,KAAK,IAAI,EAC3C,KAAK,cAAgB,KAAK,cAAc,KAAK,IAAI,CACnD,CAEA,mBAAoB,CAClB,KAAK,eAAe,QAAQ,IAAI,EAChC,OAAO,iBAAiB,UAAW,KAAK,UAAU,EAClD,KAAK,OAAO,iBAAiB,OAAQ,KAAK,aAAa,EAEnD,KAAK,aAAa,UAAU,GAAG,KAAK,eAAe,EACnD,KAAK,aAAa,QAAQ,GAAG,KAAK,aAAa,EAC/C,KAAK,aAAa,WAAW,GAC/B,KAAK,yBAAyB,KAAK,aAAa,WAAW,CAAE,EAC3D,KAAK,aAAa,KAAK,IAAG,KAAK,OAAO,IAAM,KAAK,aAAa,KAAK,EACzE,CAEA,sBAAuB,CACrB,KAAK,eAAe,WAAW,EAC/B,OAAO,oBAAoB,UAAW,KAAK,UAAU,EACrD,KAAK,OAAO,oBAAoB,OAAQ,KAAK,aAAa,EACtD,KAAK,gBAAgB,cAAc,KAAK,cAAc,EAC1D,KAAK,aAAa,QAAQ,EAC1B,QAAWC,KAAK,KAAK,aACnBA,EAAE,GAAG,MAAM,EACXA,EAAE,GAAG,IAAM,GAEb,KAAK,aAAe,CAAC,CACvB,CAEA,yBAAyBC,EAAcC,EAAqBC,EAAoB,CAC9E,OAAQF,EAAM,CACZ,IAAK,MACCE,IACF,KAAK,OAAS,GACd,KAAK,OAAO,IAAMA,GAEpB,MACF,IAAK,QACH,KAAK,kBAAoB,SAASA,GAAO,OAAQ,EAAE,EACnD,KAAK,aAAa,EAClB,MACF,IAAK,SACH,KAAK,mBAAqB,SAASA,GAAO,OAAQ,EAAE,EACpD,KAAK,aAAa,EAClB,MACF,IAAK,WACCA,IAAQ,KAAM,KAAK,eAAe,GAEpC,KAAK,aAAa,QAAQ,EAC1B,KAAK,YAAc,MAErB,MACF,IAAK,SACH,KAAK,aAAa,EAClB,MACF,IAAK,gBAAiB,CACpB,IAAMC,EAAO,WAAWD,GAAO,GAAG,EAClC,QAAWH,KAAK,KAAK,aAAcA,EAAE,GAAG,aAAeI,EACvD,KAAK,aAAa,oBAAqB,CAAE,aAAcA,CAAK,CAAC,EAC7D,KAAK,aAAa,YAAYA,CAAI,EAClC,KAAK,cAAc,IAAI,MAAM,YAAY,CAAC,EAC1C,KACF,CACA,IAAK,QACH,QAAWJ,KAAK,KAAK,aAAcA,EAAE,GAAG,MAAQG,IAAQ,KACxD,KAAK,aAAa,YAAa,CAAE,MAAOA,IAAQ,IAAK,CAAC,EACtD,MACF,IAAK,YACCA,GAAK,KAAK,yBAAyBA,CAAG,EAC1C,KACJ,CACF,CAyBA,IAAI,eAAmC,CACrC,OAAO,KAAK,MACd,CAEA,MAAO,CACL,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,aAAa,MAAM,EACxB,KAAK,QAAU,GACf,KAAK,aAAa,cAAc,EAAI,EACpC,KAAK,cAAc,IAAI,MAAM,MAAM,CAAC,CACtC,CAEA,OAAQ,CACN,KAAK,kBAAkB,EACvB,KAAK,aAAa,OAAO,EACzB,KAAK,QAAU,GACf,KAAK,aAAa,cAAc,EAAK,EACrC,KAAK,cAAc,IAAI,MAAM,OAAO,CAAC,CACvC,CAEA,KAAKE,EAAuB,CAC1B,IAAMC,EAAQ,KAAK,MAAMD,EAAgBX,CAAW,EACpD,KAAK,aAAa,OAAQ,CAAE,MAAAY,CAAM,CAAC,EACnC,KAAK,aAAeD,EAGpB,QAAWL,KAAK,KAAK,aAAc,CACjC,IAAMO,EAAUF,EAAgBL,EAAE,MAC9BO,GAAW,GAAKA,EAAUP,EAAE,WAC9BA,EAAE,GAAG,YAAcO,EAEvB,CAEA,KAAK,QAAU,GACf,KAAK,aAAa,cAAc,EAAK,EACrC,KAAK,aAAa,WAAW,KAAK,aAAc,KAAK,SAAS,CAChE,CAEA,IAAI,aAAc,CAChB,OAAO,KAAK,YACd,CACA,IAAI,YAAYC,EAAW,CACzB,KAAK,KAAKA,CAAC,CACb,CAEA,IAAI,UAAW,CACb,OAAO,KAAK,SACd,CACA,IAAI,QAAS,CACX,OAAO,KAAK,OACd,CACA,IAAI,OAAQ,CACV,OAAO,KAAK,MACd,CAEA,IAAI,cAAe,CACjB,OAAO,WAAW,KAAK,aAAa,eAAe,GAAK,GAAG,CAC7D,CACA,IAAI,aAAaC,EAAW,CAC1B,KAAK,aAAa,gBAAiB,OAAOA,CAAC,CAAC,CAC9C,CAEA,IAAI,OAAQ,CACV,OAAO,KAAK,aAAa,OAAO,CAClC,CACA,IAAI,MAAMT,EAAY,CAChBA,EAAG,KAAK,aAAa,QAAS,EAAE,EAC/B,KAAK,gBAAgB,OAAO,CACnC,CAEA,IAAI,MAAO,CACT,OAAO,KAAK,aAAa,MAAM,CACjC,CACA,IAAI,KAAKU,EAAY,CACfA,EAAG,KAAK,aAAa,OAAQ,EAAE,EAC9B,KAAK,gBAAgB,MAAM,CAClC,CAIQ,aAAaC,EAAgBC,EAAiC,CAAC,EAAG,CACxE,GAAI,CACF,KAAK,OAAO,eAAe,YACzB,CAAE,OAAQ,YAAa,KAAM,UAAW,OAAAD,EAAQ,GAAGC,CAAM,EACzD,GACF,CACF,MAAQ,CAER,CACF,CAEQ,iBAAiBb,EAAc,CACrC,OAAOA,EACJ,aAAa,EACb,KAAMc,GAAWA,aAAkB,aAAeA,EAAO,UAAU,SAAS,cAAc,CAAC,CAChG,CAEQ,WAAW,EAAiB,CAClC,GAAI,EAAE,SAAW,KAAK,OAAO,cAAe,OAC5C,IAAMC,EAAO,EAAE,KACf,GAAI,GAACA,GAAQA,EAAK,SAAW,cAE7B,IAAIA,EAAK,OAAS,QAAS,CACzB,KAAK,cAAgBA,EAAK,OAAS,GAAKpB,EACxC,IAAMqB,EAAa,CAAC,KAAK,QACzB,KAAK,QAAU,CAACD,EAAK,UAIjBC,GAAc,KAAK,QACrB,KAAK,kBAAkB,EACd,CAACA,GAAc,CAAC,KAAK,SAC9B,KAAK,iBAAiB,EAIxB,IAAMC,EAAM,YAAY,IAAI,GACxBA,EAAM,KAAK,cAAgB,KAAO,KAAK,UAAYD,KACrD,KAAK,cAAgBC,EACrB,KAAK,aAAa,WAAW,KAAK,aAAc,KAAK,SAAS,EAC9D,KAAK,aAAa,cAAc,CAAC,KAAK,OAAO,EAC7C,KAAK,cACH,IAAI,YAAY,aAAc,CAAE,OAAQ,CAAE,YAAa,KAAK,YAAa,CAAE,CAAC,CAC9E,GAGE,KAAK,cAAgB,KAAK,WAAa,CAAC,KAAK,UAC/C,KAAK,kBAAkB,EACnB,KAAK,MACP,KAAK,KAAK,CAAC,EACX,KAAK,KAAK,IAEV,KAAK,QAAU,GACf,KAAK,aAAa,cAAc,EAAK,EACrC,KAAK,cAAc,IAAI,MAAM,OAAO,CAAC,GAG3C,CAEIF,EAAK,OAAS,YAAcA,EAAK,iBAAmB,GAGlD,OAAO,SAASA,EAAK,gBAAgB,IACvC,KAAK,UAAYA,EAAK,iBAAmBpB,EACzC,KAAK,aAAa,WAAW,KAAK,aAAc,KAAK,SAAS,GAI9DoB,EAAK,OAAS,cAAgBA,EAAK,MAAQ,GAAKA,EAAK,OAAS,IAChE,KAAK,kBAAoBA,EAAK,MAC9B,KAAK,mBAAqBA,EAAK,OAC/B,KAAK,aAAa,GAEtB,CAEQ,iBAAmB,GAEnB,eAAgB,CACtB,IAAIG,EAAW,EACf,KAAK,iBAAmB,GACpB,KAAK,gBAAgB,cAAc,KAAK,cAAc,EAE1D,KAAK,eAAiB,YAAY,IAAM,CACtCA,IACA,GAAI,CACF,IAAMC,EAAM,KAAK,OAAO,cAKxB,GAAI,CAACA,EAAK,OAGV,IAAMC,EAAa,CAAC,EAAED,EAAI,MAAQA,EAAI,UAChCE,EAAe,CAAC,EAAEF,EAAI,aAAe,OAAO,KAAKA,EAAI,WAAW,EAAE,OAAS,GAGjF,GAAI,CAACC,GAAcC,GAAgB,CAAC,KAAK,kBAAoBH,GAAY,EAAG,CAC1E,KAAK,eAAe,EACpB,MACF,CAGA,GAAI,KAAK,kBAAoB,CAACE,EAC5B,OAwBF,IAAME,GArBa,IAAM,CACvB,GAAIH,EAAI,UAAY,OAAOA,EAAI,SAAS,aAAgB,WAAY,OAAOA,EAAI,SAC/E,GAAIA,EAAI,YAAa,CACnB,IAAMI,EAAO,OAAO,KAAKJ,EAAI,WAAW,EACxC,GAAII,EAAK,OAAS,EAAG,CAMnB,IAAMC,EAAS,KAAK,OAAO,iBACvB,cAAc,uBAAuB,GACrC,aAAa,qBAAqB,EAChCC,EAAMD,GAAUA,KAAUL,EAAI,YAAcK,EAASD,EAAKA,EAAK,OAAS,CAAC,EACzEG,EAAKP,EAAI,YAAYM,CAAG,EAC9B,MAAO,CAAE,YAAa,IAAMC,EAAG,SAAS,CAAE,CAC5C,CACF,CACA,OAAO,IACT,GAE2B,EAC3B,GAAIJ,GAAWA,EAAQ,YAAY,EAAI,EAAG,CACxC,cAAc,KAAK,cAAe,EAClC,KAAK,UAAYA,EAAQ,YAAY,EACrC,KAAK,OAAS,GACd,KAAK,aAAa,WAAW,EAAG,KAAK,SAAS,EAC9C,KAAK,cAAc,IAAI,YAAY,QAAS,CAAE,OAAQ,CAAE,SAAU,KAAK,SAAU,CAAE,CAAC,CAAC,EAIrF,IAAMK,EADM,KAAK,OAAO,iBACN,cAAc,uBAAuB,EACvD,GAAIA,EAAM,CACR,IAAMC,EAAI,SAASD,EAAK,aAAa,YAAY,GAAK,IAAK,EAAE,EACvDE,EAAI,SAASF,EAAK,aAAa,aAAa,GAAK,IAAK,EAAE,EAC1DC,EAAI,GAAKC,EAAI,IACf,KAAK,kBAAoBD,EACzB,KAAK,mBAAqBC,EAC1B,KAAK,aAAa,EAEtB,CAEA,KAAK,kBAAkB,EAEnB,KAAK,aAAa,UAAU,GAC9B,KAAK,KAAK,EAEZ,MACF,CACF,MAAQ,CAER,CAEIX,GAAY,KACd,cAAc,KAAK,cAAe,EAClC,KAAK,cACH,IAAI,YAAY,QAAS,CACvB,OAAQ,CAAE,QAAS,yCAA0C,CAC/D,CAAC,CACH,EAEJ,EAAG,GAAG,CACR,CAGQ,gBAAiB,CACvB,KAAK,iBAAmB,GACxB,GAAI,CACF,IAAMY,EAAM,KAAK,OAAO,gBACxB,GAAI,CAACA,EAAK,OACV,IAAMC,EAASD,EAAI,cAAc,QAAQ,EACzCC,EAAO,IAAMnC,EACbmC,EAAO,OAAS,IAAM,CAEtB,EACAA,EAAO,QAAU,IAAM,CAEvB,GACCD,EAAI,MAAQA,EAAI,iBAAiB,YAAYC,CAAM,CACtD,MAAQ,CAER,CACF,CAEQ,cAAe,CACrB,IAAMC,EAAO,KAAK,sBAAsB,EACxC,GAAIA,EAAK,QAAU,GAAKA,EAAK,SAAW,EAAG,OAC3C,IAAMC,EAAQ,KAAK,IACjBD,EAAK,MAAQ,KAAK,kBAClBA,EAAK,OAAS,KAAK,kBACrB,EACA,KAAK,OAAO,MAAM,MAAQ,GAAG,KAAK,iBAAiB,KACnD,KAAK,OAAO,MAAM,OAAS,GAAG,KAAK,kBAAkB,KACrD,KAAK,OAAO,MAAM,UAAY,+BAA+BC,CAAK,GACpE,CAEQ,gBAAiB,CACvB,GAAI,KAAK,YAAa,OACtB,IAAMC,EAA+B,CACnC,OAAQ,IAAM,KAAK,KAAK,EACxB,QAAS,IAAM,KAAK,MAAM,EAC1B,OAASC,GAAa,KAAK,KAAKA,EAAW,KAAK,SAAS,EACzD,cAAgBC,GAAU,CACxB,KAAK,aAAeA,CACtB,CACF,EACMC,EAAc,KAAK,aAAa,eAAe,EAC/CC,EAAeD,EACjBA,EACG,MAAM,GAAG,EACT,IAAI,MAAM,EACV,OAAQE,GAAM,CAAC,MAAMA,CAAC,GAAKA,EAAI,CAAC,EACnC,OACJ,KAAK,YAAcC,EAAe,KAAK,OAAQN,EAAW,CAAE,aAAAI,CAAa,CAAC,CAC5E,CAEQ,cAAe,CACrB,IAAMG,EAAM,KAAK,aAAa,QAAQ,EACtC,GAAI,CAACA,EAAK,CACR,KAAK,UAAU,OAAO,EACtB,KAAK,SAAW,KAChB,MACF,CACK,KAAK,WACR,KAAK,SAAW,SAAS,cAAc,KAAK,EAC5C,KAAK,SAAS,UAAY,aAC1B,KAAK,OAAO,YAAY,KAAK,QAAQ,GAEvC,KAAK,SAAS,IAAMA,CACtB,CAEQ,kBAAmB,CACzB,QAAWxC,KAAK,KAAK,aACfA,EAAE,GAAG,KAAKA,EAAE,GAAG,KAAK,EAAE,MAAM,IAAM,CAAC,CAAC,CAE5C,CAEQ,mBAAoB,CAC1B,QAAWA,KAAK,KAAK,aAAcA,EAAE,GAAG,MAAM,CAChD,CAGQ,mBAAmByC,EAAaC,EAAwBC,EAAeC,EAAkB,CAE/F,GAAI,KAAK,aAAa,KAAM5C,GAAMA,EAAE,GAAG,MAAQyC,CAAG,EAAG,OAErD,IAAMI,EAAKH,IAAQ,QAAU,SAAS,cAAc,OAAO,EAAI,IAAI,MACnEG,EAAG,QAAU,OACbA,EAAG,IAAMJ,EACTI,EAAG,KAAK,EACRA,EAAG,MAAQ,KAAK,MACZ,KAAK,eAAiB,IAAGA,EAAG,aAAe,KAAK,cAEpD,KAAK,aAAa,KAAK,CAAE,GAAAA,EAAI,MAAAF,EAAO,SAAAC,CAAS,CAAC,CAChD,CAOQ,yBAAyBE,EAAkB,CACjD,KAAK,mBAAmBA,EAAU,QAAS,EAAG,GAAQ,CACxD,CAUQ,mBAAoB,CAC1B,GAAI,CACF,IAAMjB,EAAM,KAAK,OAAO,gBACxB,GAAI,CAACA,EAAK,OAGV,IAAMkB,EAAWlB,EAAI,iBACnB,sCACF,EAEA,QAAWmB,KAAYD,EAAU,CAC/B,IAAMN,EAAMO,EAAS,aAAa,KAAK,GAAKA,EAAS,cAAc,QAAQ,GAAG,IAC9E,GAAI,CAACP,EAAK,SAEV,IAAME,EAAQ,WAAWK,EAAS,aAAa,YAAY,GAAK,GAAG,EAC7DJ,EAAW,WAAWI,EAAS,aAAa,eAAe,GAAK,UAAU,EAC1EN,EAAMM,EAAS,UAAY,QAAW,QAAqB,QAEjE,KAAK,mBAAmBP,EAAKC,EAAKC,EAAOC,CAAQ,EAGjDI,EAAS,gBAAgB,KAAK,EAC9BA,EAAS,gBAAgB,YAAY,EACrCA,EAAS,gBAAgB,eAAe,EACxCA,EAAS,iBAAiB,QAAQ,EAAE,QAASC,GAAMA,EAAE,OAAO,CAAC,CAC/D,CACF,MAAQ,CAER,CACF,CAEQ,aAAc,CACpB,KAAK,UAAU,OAAO,EACtB,KAAK,SAAW,IAClB,CACF,EAEK,eAAe,IAAI,oBAAoB,GAC1C,eAAe,OAAO,qBAAsBrD,CAAiB","names":["hyperframes_player_exports","__export","HyperframesPlayer","SPEED_PRESETS","formatSpeed","formatTime","__toCommonJS","PLAYER_STYLES","PLAY_ICON","PAUSE_ICON","SPEED_PRESETS","formatSpeed","speed","formatTime","seconds","s","m","sec","createControls","parent","callbacks","options","presets","controls","e","playBtn","PLAY_ICON","scrubber","progress","time","speedWrap","speedBtn","speedMenu","preset","item","isPlaying","hideTimeout","speedIndex","setActiveOption","opt","isOpen","target","newSpeed","onDocClick","handleScrubAt","clientX","rect","fraction","scrubbing","onMouseMove","onMouseUp","touch","onTouchMove","onTouchEnd","startHideTimer","host","current","duration","pct","playing","PAUSE_ICON","idx","DEFAULT_FPS","RUNTIME_CDN_URL","HyperframesPlayer","style","PLAYER_STYLES","event","m","name","_old","val","rate","timeInSeconds","frame","relTime","t","r","l","action","extra","target","data","wasPlaying","now","attempts","win","hasRuntime","hasTimelines","adapter","keys","rootId","key","tl","root","w","h","doc","script","rect","scale","callbacks","fraction","speed","presetsAttr","speedPresets","n","createControls","url","src","tag","start","duration","el","audioSrc","mediaEls","iframeEl","s"]}
|
|
1
|
+
{"version":3,"sources":["../src/hyperframes-player.ts","../src/styles.ts","../src/controls.ts"],"sourcesContent":["import { createControls, SPEED_PRESETS, type ControlsCallbacks } from \"./controls.js\";\nimport { PLAYER_STYLES } from \"./styles.js\";\n\nconst DEFAULT_FPS = 30;\nconst RUNTIME_CDN_URL =\n \"https://cdn.jsdelivr.net/npm/@hyperframes/core/dist/hyperframe.runtime.iife.js\";\n\nclass HyperframesPlayer extends HTMLElement {\n static get observedAttributes() {\n return [\"src\", \"width\", \"height\", \"controls\", \"muted\", \"poster\", \"playback-rate\", \"audio-src\"];\n }\n\n private shadow: ShadowRoot;\n private container: HTMLDivElement;\n private iframe: HTMLIFrameElement;\n private posterEl: HTMLImageElement | null = null;\n private controlsApi: ReturnType<typeof createControls> | null = null;\n private resizeObserver: ResizeObserver;\n\n private _ready = false;\n private _duration = 0;\n private _currentTime = 0;\n private _paused = true;\n private _compositionWidth = 1920;\n private _compositionHeight = 1080;\n private _probeInterval: ReturnType<typeof setInterval> | null = null;\n private _lastUpdateMs = 0;\n\n /**\n * Parent-frame media elements for mobile playback.\n *\n * Mobile browsers block media.play() inside iframes when the user gesture\n * happened in the parent frame — postMessage doesn't transfer user activation\n * (per the User Activation v2 spec). We extract ALL media sources from the\n * iframe's timed elements (audio/video with data-start), play them in the\n * parent frame (where the gesture lives), and disable the iframe copies.\n */\n private _parentMedia: Array<{\n el: HTMLMediaElement;\n start: number;\n duration: number;\n }> = [];\n\n constructor() {\n super();\n this.shadow = this.attachShadow({ mode: \"open\" });\n\n const style = document.createElement(\"style\");\n style.textContent = PLAYER_STYLES;\n this.shadow.appendChild(style);\n\n this.container = document.createElement(\"div\");\n this.container.className = \"hfp-container\";\n\n this.iframe = document.createElement(\"iframe\");\n this.iframe.className = \"hfp-iframe\";\n this.iframe.sandbox.add(\"allow-scripts\", \"allow-same-origin\");\n this.iframe.allow = \"autoplay; fullscreen\";\n this.iframe.referrerPolicy = \"no-referrer\";\n this.iframe.title = \"HyperFrames Composition\";\n\n this.container.appendChild(this.iframe);\n this.shadow.appendChild(this.container);\n\n // Clicking the bare player surface toggles play/pause.\n // Ignore shadow-DOM control interactions so overlay clicks don't double-handle.\n this.addEventListener(\"click\", (event) => {\n if (this._isControlsClick(event)) return;\n if (this._paused) this.play();\n else this.pause();\n });\n\n this.resizeObserver = new ResizeObserver(() => this._updateScale());\n\n this._onMessage = this._onMessage.bind(this);\n this._onIframeLoad = this._onIframeLoad.bind(this);\n }\n\n connectedCallback() {\n this.resizeObserver.observe(this);\n window.addEventListener(\"message\", this._onMessage);\n this.iframe.addEventListener(\"load\", this._onIframeLoad);\n\n if (this.hasAttribute(\"controls\")) this._setupControls();\n if (this.hasAttribute(\"poster\")) this._setupPoster();\n if (this.hasAttribute(\"audio-src\"))\n this._setupParentAudioFromUrl(this.getAttribute(\"audio-src\")!);\n if (this.hasAttribute(\"src\")) this.iframe.src = this.getAttribute(\"src\")!;\n }\n\n disconnectedCallback() {\n this.resizeObserver.disconnect();\n window.removeEventListener(\"message\", this._onMessage);\n this.iframe.removeEventListener(\"load\", this._onIframeLoad);\n if (this._probeInterval) clearInterval(this._probeInterval);\n this.controlsApi?.destroy();\n for (const m of this._parentMedia) {\n m.el.pause();\n m.el.src = \"\";\n }\n this._parentMedia = [];\n }\n\n attributeChangedCallback(name: string, _old: string | null, val: string | null) {\n switch (name) {\n case \"src\":\n if (val) {\n this._ready = false;\n this.iframe.src = val;\n }\n break;\n case \"width\":\n this._compositionWidth = parseInt(val || \"1920\", 10);\n this._updateScale();\n break;\n case \"height\":\n this._compositionHeight = parseInt(val || \"1080\", 10);\n this._updateScale();\n break;\n case \"controls\":\n if (val !== null) this._setupControls();\n else {\n this.controlsApi?.destroy();\n this.controlsApi = null;\n }\n break;\n case \"poster\":\n this._setupPoster();\n break;\n case \"playback-rate\": {\n const rate = parseFloat(val || \"1\");\n for (const m of this._parentMedia) m.el.playbackRate = rate;\n this._sendControl(\"set-playback-rate\", { playbackRate: rate });\n this.controlsApi?.updateSpeed(rate);\n this.dispatchEvent(new Event(\"ratechange\"));\n break;\n }\n case \"muted\":\n for (const m of this._parentMedia) m.el.muted = val !== null;\n this._sendControl(\"set-muted\", { muted: val !== null });\n break;\n case \"audio-src\":\n if (val) this._setupParentAudioFromUrl(val);\n break;\n }\n }\n\n // ── Public API ──\n\n /**\n * Access the inner `<iframe>` element rendering the composition.\n *\n * Use this when integrating the player with editors, recorders, or\n * timeline tools (e.g. `@hyperframes/studio`) that need to inspect\n * the composition's DOM or read its `__player` / `__timelines`\n * runtime objects.\n *\n * **Common pitfall:** the iframe lives inside the player's Shadow DOM.\n * Passing the `<hyperframes-player>` element itself to code that expects\n * an `<iframe>` will silently break — `.contentWindow` returns `null`.\n * Always extract `iframeElement` first:\n *\n * ```ts\n * // ❌ Wrong — element ref doesn't expose contentWindow\n * iframeRef.current = playerRef.current;\n *\n * // ✓ Right — bridge the actual iframe\n * iframeRef.current = playerRef.current.iframeElement;\n * ```\n */\n get iframeElement(): HTMLIFrameElement {\n return this.iframe;\n }\n\n play() {\n this._hidePoster();\n this._playParentMedia();\n this._sendControl(\"play\");\n this._paused = false;\n this.controlsApi?.updatePlaying(true);\n this.dispatchEvent(new Event(\"play\"));\n }\n\n pause() {\n this._pauseParentMedia();\n this._sendControl(\"pause\");\n this._paused = true;\n this.controlsApi?.updatePlaying(false);\n this.dispatchEvent(new Event(\"pause\"));\n }\n\n seek(timeInSeconds: number) {\n const frame = Math.round(timeInSeconds * DEFAULT_FPS);\n this._sendControl(\"seek\", { frame });\n this._currentTime = timeInSeconds;\n\n // Sync parent media positions (accounting for each element's start offset)\n for (const m of this._parentMedia) {\n const relTime = timeInSeconds - m.start;\n if (relTime >= 0 && relTime < m.duration) {\n m.el.currentTime = relTime;\n }\n }\n\n this._paused = true;\n this.controlsApi?.updatePlaying(false);\n this.controlsApi?.updateTime(this._currentTime, this._duration);\n }\n\n get currentTime() {\n return this._currentTime;\n }\n set currentTime(t: number) {\n this.seek(t);\n }\n\n get duration() {\n return this._duration;\n }\n get paused() {\n return this._paused;\n }\n get ready() {\n return this._ready;\n }\n\n get playbackRate() {\n return parseFloat(this.getAttribute(\"playback-rate\") || \"1\");\n }\n set playbackRate(r: number) {\n this.setAttribute(\"playback-rate\", String(r));\n }\n\n get muted() {\n return this.hasAttribute(\"muted\");\n }\n set muted(m: boolean) {\n if (m) this.setAttribute(\"muted\", \"\");\n else this.removeAttribute(\"muted\");\n }\n\n get loop() {\n return this.hasAttribute(\"loop\");\n }\n set loop(l: boolean) {\n if (l) this.setAttribute(\"loop\", \"\");\n else this.removeAttribute(\"loop\");\n }\n\n // ── Private ──\n\n private _sendControl(action: string, extra: Record<string, unknown> = {}) {\n try {\n this.iframe.contentWindow?.postMessage(\n { source: \"hf-parent\", type: \"control\", action, ...extra },\n \"*\",\n );\n } catch {\n /* cross-origin */\n }\n }\n\n private _isControlsClick(event: Event) {\n return event\n .composedPath()\n .some((target) => target instanceof HTMLElement && target.classList.contains(\"hfp-controls\"));\n }\n\n private _onMessage(e: MessageEvent) {\n if (e.source !== this.iframe.contentWindow) return;\n const data = e.data;\n if (!data || data.source !== \"hf-preview\") return;\n\n if (data.type === \"state\") {\n this._currentTime = (data.frame ?? 0) / DEFAULT_FPS;\n const wasPlaying = !this._paused;\n this._paused = !data.isPlaying;\n\n // Sync parent media on runtime play/pause transitions (e.g. browser\n // throttling, visibility change, or scrubber interaction in the iframe).\n if (wasPlaying && this._paused) {\n this._pauseParentMedia();\n } else if (!wasPlaying && !this._paused) {\n this._playParentMedia();\n }\n\n // Throttle UI updates and event dispatch to ~10fps to avoid excessive re-renders\n const now = performance.now();\n if (now - this._lastUpdateMs > 100 || this._paused !== wasPlaying) {\n this._lastUpdateMs = now;\n this.controlsApi?.updateTime(this._currentTime, this._duration);\n this.controlsApi?.updatePlaying(!this._paused);\n this.dispatchEvent(\n new CustomEvent(\"timeupdate\", { detail: { currentTime: this._currentTime } }),\n );\n }\n\n if (this._currentTime >= this._duration && !this._paused) {\n this._pauseParentMedia();\n if (this.loop) {\n this.seek(0);\n this.play();\n } else {\n this._paused = true;\n this.controlsApi?.updatePlaying(false);\n this.dispatchEvent(new Event(\"ended\"));\n }\n }\n }\n\n if (data.type === \"timeline\" && data.durationInFrames > 0) {\n // Ignore Infinity duration from runtime (caused by loop-inflated timelines without data-duration)\n // The player already has duration from the initial probe, so keep that.\n if (Number.isFinite(data.durationInFrames)) {\n this._duration = data.durationInFrames / DEFAULT_FPS;\n this.controlsApi?.updateTime(this._currentTime, this._duration);\n }\n }\n\n if (data.type === \"stage-size\" && data.width > 0 && data.height > 0) {\n this._compositionWidth = data.width;\n this._compositionHeight = data.height;\n this._updateScale();\n }\n }\n\n private _runtimeInjected = false;\n\n private _onIframeLoad() {\n let attempts = 0;\n this._runtimeInjected = false;\n if (this._probeInterval) clearInterval(this._probeInterval);\n\n this._probeInterval = setInterval(() => {\n attempts++;\n try {\n const win = this.iframe.contentWindow as Window & {\n __player?: { getDuration: () => number };\n __timelines?: Record<string, { duration: () => number }>;\n __hf?: unknown;\n };\n if (!win) return;\n\n // Check if the runtime bridge is active (__hf or __player from the runtime)\n const hasRuntime = !!(win.__hf || win.__player);\n const hasTimelines = !!(win.__timelines && Object.keys(win.__timelines).length > 0);\n\n // Auto-inject runtime if GSAP timelines exist but no runtime bridge\n if (!hasRuntime && hasTimelines && !this._runtimeInjected && attempts >= 5) {\n this._injectRuntime();\n return; // Wait for runtime to load and initialize\n }\n\n // Runtime was injected but hasn't loaded yet — keep waiting\n if (this._runtimeInjected && !hasRuntime) {\n return;\n }\n\n const getAdapter = () => {\n if (win.__player && typeof win.__player.getDuration === \"function\") return win.__player;\n if (win.__timelines) {\n const keys = Object.keys(win.__timelines);\n if (keys.length > 0) {\n // Resolve the root composition id from the DOM — the outermost\n // `[data-composition-id]` element is the master. Bundled previews\n // register the root composition alongside sub-compositions, and\n // without this lookup Object.keys() order would make a\n // sub-composition's duration hijack the overall video length.\n const rootId = this.iframe.contentDocument\n ?.querySelector(\"[data-composition-id]\")\n ?.getAttribute(\"data-composition-id\");\n const key = rootId && rootId in win.__timelines ? rootId : keys[keys.length - 1];\n const tl = win.__timelines[key];\n return { getDuration: () => tl.duration() };\n }\n }\n return null;\n };\n\n const adapter = getAdapter();\n if (adapter && adapter.getDuration() > 0) {\n clearInterval(this._probeInterval!);\n this._duration = adapter.getDuration();\n this._ready = true;\n this.controlsApi?.updateTime(0, this._duration);\n this.dispatchEvent(new CustomEvent(\"ready\", { detail: { duration: this._duration } }));\n\n // Auto-detect dimensions from composition\n const doc = this.iframe.contentDocument;\n const root = doc?.querySelector(\"[data-composition-id]\");\n if (root) {\n const w = parseInt(root.getAttribute(\"data-width\") || \"0\", 10);\n const h = parseInt(root.getAttribute(\"data-height\") || \"0\", 10);\n if (w > 0 && h > 0) {\n this._compositionWidth = w;\n this._compositionHeight = h;\n this._updateScale();\n }\n }\n\n this._setupParentMedia();\n\n if (this.hasAttribute(\"autoplay\")) {\n this.play();\n }\n return;\n }\n } catch {\n /* cross-origin */\n }\n\n if (attempts >= 40) {\n clearInterval(this._probeInterval!);\n this.dispatchEvent(\n new CustomEvent(\"error\", {\n detail: { message: \"Composition timeline not found after 8s\" },\n }),\n );\n }\n }, 200);\n }\n\n /** Inject the HyperFrames runtime into the iframe if not already present. */\n private _injectRuntime() {\n this._runtimeInjected = true;\n try {\n const doc = this.iframe.contentDocument;\n if (!doc) return;\n const script = doc.createElement(\"script\");\n script.src = RUNTIME_CDN_URL;\n script.onload = () => {\n // Runtime loaded — the probe interval will pick up __hf on next tick\n };\n script.onerror = () => {\n // CDN failed — the probe will continue and eventually timeout\n };\n (doc.head || doc.documentElement).appendChild(script);\n } catch {\n /* cross-origin — can't inject */\n }\n }\n\n private _updateScale() {\n const rect = this.getBoundingClientRect();\n if (rect.width === 0 || rect.height === 0) return;\n const scale = Math.min(\n rect.width / this._compositionWidth,\n rect.height / this._compositionHeight,\n );\n this.iframe.style.width = `${this._compositionWidth}px`;\n this.iframe.style.height = `${this._compositionHeight}px`;\n this.iframe.style.transform = `translate(-50%, -50%) scale(${scale})`;\n }\n\n private _setupControls() {\n if (this.controlsApi) return;\n const callbacks: ControlsCallbacks = {\n onPlay: () => this.play(),\n onPause: () => this.pause(),\n onSeek: (fraction) => this.seek(fraction * this._duration),\n onSpeedChange: (speed) => {\n this.playbackRate = speed;\n },\n };\n const presetsAttr = this.getAttribute(\"speed-presets\");\n const speedPresets = presetsAttr\n ? presetsAttr\n .split(\",\")\n .map(Number)\n .filter((n) => !isNaN(n) && n > 0)\n : undefined;\n this.controlsApi = createControls(this.shadow, callbacks, { speedPresets });\n }\n\n private _setupPoster() {\n const url = this.getAttribute(\"poster\");\n if (!url) {\n this.posterEl?.remove();\n this.posterEl = null;\n return;\n }\n if (!this.posterEl) {\n this.posterEl = document.createElement(\"img\");\n this.posterEl.className = \"hfp-poster\";\n this.shadow.appendChild(this.posterEl);\n }\n this.posterEl.src = url;\n }\n\n private _playParentMedia() {\n for (const m of this._parentMedia) {\n if (m.el.src) {\n m.el\n .play()\n .then(() => {\n // Parent play succeeded — mute the iframe copy to prevent double audio.\n // This runs asynchronously, so the runtime may briefly play both copies,\n // but the overlap is inaudible (same audio at the same position).\n this._muteIframeMedia();\n })\n .catch(() => {});\n }\n }\n }\n\n private _muteIframeMedia() {\n try {\n const doc = this.iframe.contentDocument;\n if (!doc) return;\n const mediaEls = doc.querySelectorAll<HTMLMediaElement>(\n \"audio[data-start], video[data-start]\",\n );\n for (const el of mediaEls) el.volume = 0;\n } catch {\n // cross-origin\n }\n }\n\n private _pauseParentMedia() {\n for (const m of this._parentMedia) m.el.pause();\n }\n\n /** Create a parent-frame media element, configure it, and start preloading. */\n private _createParentMedia(src: string, tag: \"audio\" | \"video\", start: number, duration: number) {\n // Deduplicate — browsers normalize URLs so we compare on the element after assignment\n if (this._parentMedia.some((m) => m.el.src === src)) return;\n\n const el = tag === \"video\" ? document.createElement(\"video\") : new Audio();\n el.preload = \"auto\";\n el.src = src;\n el.load();\n el.muted = this.muted;\n if (this.playbackRate !== 1) el.playbackRate = this.playbackRate;\n\n this._parentMedia.push({ el, start, duration });\n }\n\n /**\n * Set up a single parent-frame audio from an explicit URL (via `audio-src`).\n * Convenience for the common single-narration case — starts preloading\n * immediately without waiting for the iframe to load.\n */\n private _setupParentAudioFromUrl(audioSrc: string) {\n this._createParentMedia(audioSrc, \"audio\", 0, Infinity);\n }\n\n /**\n * Extract ALL timed media (audio/video with data-start) from the iframe's\n * DOM and create parent-frame copies. Disables the iframe originals so the\n * runtime doesn't try to play them (which would fail on mobile and cause\n * double playback on desktop).\n *\n * If `audio-src` was already set, this just disables the iframe media.\n */\n private _setupParentMedia() {\n try {\n const doc = this.iframe.contentDocument;\n if (!doc) return;\n\n // Find all timed media — matches the runtime's media.ts selector\n const mediaEls = doc.querySelectorAll<HTMLMediaElement>(\n \"audio[data-start], video[data-start]\",\n );\n\n for (const iframeEl of mediaEls) {\n const src = iframeEl.getAttribute(\"src\") || iframeEl.querySelector(\"source\")?.src;\n if (!src) continue;\n\n const start = parseFloat(iframeEl.getAttribute(\"data-start\") || \"0\");\n const duration = parseFloat(iframeEl.getAttribute(\"data-duration\") || \"Infinity\");\n const tag = iframeEl.tagName === \"VIDEO\" ? (\"video\" as const) : (\"audio\" as const);\n\n this._createParentMedia(src, tag, start, duration);\n\n // DO NOT strip data-start, data-duration, or src from the iframe elements.\n // The runtime's syncRuntimeMedia queries audio[data-start] — removing these\n // attributes makes the runtime unable to find, sync, or play media clips.\n // The iframe copies remain fully functional for the runtime. On mobile,\n // parent copies provide the audible output via the component's play() method.\n // On desktop and in the studio (which calls __player.play() directly),\n // the runtime's own media sync handles playback.\n }\n } catch {\n // Cross-origin iframe — can't access DOM, fall back to iframe media\n }\n }\n\n private _hidePoster() {\n this.posterEl?.remove();\n this.posterEl = null;\n }\n}\n\nif (!customElements.get(\"hyperframes-player\")) {\n customElements.define(\"hyperframes-player\", HyperframesPlayer);\n}\n\nexport { HyperframesPlayer };\nexport { formatTime, formatSpeed, SPEED_PRESETS } from \"./controls.js\";\nexport type { ControlsCallbacks, ControlsOptions } from \"./controls.js\";\n","export const PLAYER_STYLES = /* css */ `\n :host {\n display: block;\n position: relative;\n overflow: hidden;\n background: #000;\n contain: layout style;\n }\n\n .hfp-container {\n position: absolute;\n inset: 0;\n overflow: hidden;\n pointer-events: none;\n }\n\n\n .hfp-iframe {\n position: absolute;\n top: 50%;\n left: 50%;\n border: none;\n pointer-events: none;\n }\n\n .hfp-poster {\n position: absolute;\n inset: 0;\n object-fit: contain;\n z-index: 1;\n pointer-events: none;\n }\n\n /* ── Theming via CSS custom properties ──\n *\n * Override from outside the shadow DOM:\n * hyperframes-player {\n * --hfp-controls-bg: linear-gradient(transparent, rgba(0,0,0,0.9));\n * --hfp-accent: #ff6b6b;\n * --hfp-font: \"Inter\", sans-serif;\n * }\n */\n\n .hfp-controls {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n display: flex;\n align-items: center;\n gap: var(--hfp-controls-gap, 12px);\n padding: var(--hfp-controls-padding, 8px 16px);\n background: var(--hfp-controls-bg, linear-gradient(transparent, rgba(0, 0, 0, 0.7)));\n color: var(--hfp-color, #fff);\n font-family: var(--hfp-font, system-ui, -apple-system, sans-serif);\n font-size: var(--hfp-font-size, 13px);\n z-index: 10;\n pointer-events: auto;\n opacity: 1;\n transition: opacity 0.3s ease;\n user-select: none;\n }\n\n .hfp-controls.hfp-hidden {\n opacity: 0;\n pointer-events: none;\n }\n\n .hfp-play-btn {\n background: none;\n border: none;\n color: var(--hfp-color, #fff);\n cursor: pointer;\n padding: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n flex-shrink: 0;\n z-index: 10;\n }\n\n .hfp-play-btn:hover {\n opacity: 0.8;\n }\n\n .hfp-play-btn svg,\n .hfp-play-btn svg * {\n pointer-events: none;\n }\n\n .hfp-scrubber {\n flex: 1;\n height: var(--hfp-scrubber-height, 4px);\n background: var(--hfp-scrubber-bg, rgba(255, 255, 255, 0.3));\n border-radius: var(--hfp-scrubber-radius, 2px);\n cursor: pointer;\n position: relative;\n }\n\n .hfp-scrubber:hover {\n height: var(--hfp-scrubber-height-hover, 6px);\n }\n\n .hfp-progress {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n background: var(--hfp-accent, #fff);\n border-radius: var(--hfp-scrubber-radius, 2px);\n pointer-events: none;\n }\n\n .hfp-time {\n flex-shrink: 0;\n font-variant-numeric: tabular-nums;\n opacity: 0.9;\n }\n\n .hfp-speed-wrap {\n position: relative;\n flex-shrink: 0;\n }\n\n .hfp-speed-btn {\n background: var(--hfp-speed-btn-bg, rgba(255, 255, 255, 0.15));\n border: none;\n border-radius: var(--hfp-speed-btn-radius, 4px);\n color: var(--hfp-color, #fff);\n cursor: pointer;\n font-family: var(--hfp-font, system-ui, -apple-system, sans-serif);\n font-size: 12px;\n font-variant-numeric: tabular-nums;\n font-weight: 600;\n padding: 4px 8px;\n min-width: 40px;\n text-align: center;\n transition: background 0.15s ease;\n }\n\n .hfp-speed-btn:hover {\n background: var(--hfp-speed-btn-bg-hover, rgba(255, 255, 255, 0.3));\n }\n\n .hfp-speed-menu {\n position: absolute;\n bottom: calc(100% + 8px);\n right: 0;\n background: var(--hfp-menu-bg, rgba(20, 20, 20, 0.95));\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n border: 1px solid var(--hfp-menu-border, rgba(255, 255, 255, 0.1));\n border-radius: var(--hfp-menu-radius, 8px);\n padding: 4px;\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 80px;\n opacity: 0;\n visibility: hidden;\n transform: translateY(4px);\n transition: opacity 0.15s ease, transform 0.15s ease, visibility 0.15s;\n box-shadow: var(--hfp-menu-shadow, 0 8px 24px rgba(0, 0, 0, 0.4));\n }\n\n .hfp-speed-menu.hfp-open {\n opacity: 1;\n visibility: visible;\n transform: translateY(0);\n }\n\n .hfp-speed-option {\n background: none;\n border: none;\n border-radius: 4px;\n color: var(--hfp-menu-color, rgba(255, 255, 255, 0.7));\n cursor: pointer;\n font-family: var(--hfp-font, system-ui, -apple-system, sans-serif);\n font-size: 13px;\n font-variant-numeric: tabular-nums;\n padding: 6px 12px;\n text-align: left;\n transition: background 0.1s ease, color 0.1s ease;\n white-space: nowrap;\n }\n\n .hfp-speed-option:hover {\n background: var(--hfp-menu-hover-bg, rgba(255, 255, 255, 0.1));\n color: var(--hfp-color, #fff);\n }\n\n .hfp-speed-option.hfp-active {\n color: var(--hfp-accent, #fff);\n font-weight: 600;\n }\n`;\n\nexport const PLAY_ICON = `<svg width=\"24\" height=\"24\" viewBox=\"0 0 18 18\" fill=\"currentColor\"><polygon points=\"4,2 16,9 4,16\"/></svg>`;\nexport const PAUSE_ICON = `<svg width=\"24\" height=\"24\" viewBox=\"0 0 18 18\" fill=\"currentColor\"><rect x=\"3\" y=\"2\" width=\"4\" height=\"14\"/><rect x=\"11\" y=\"2\" width=\"4\" height=\"14\"/></svg>`;\n","import { PLAY_ICON, PAUSE_ICON } from \"./styles.js\";\n\nexport interface ControlsCallbacks {\n onPlay: () => void;\n onPause: () => void;\n onSeek: (fraction: number) => void;\n onSpeedChange: (speed: number) => void;\n}\n\n/** Default logarithmic speed presets — each step roughly doubles/halves. */\nexport const SPEED_PRESETS = [0.25, 0.5, 1, 1.5, 2, 4] as const;\n\nexport interface ControlsOptions {\n /** Speed presets shown in the menu. Defaults to SPEED_PRESETS. */\n speedPresets?: readonly number[];\n}\n\nexport function formatSpeed(speed: number): string {\n return Number.isInteger(speed) ? `${speed}x` : `${speed}x`;\n}\n\nexport function formatTime(seconds: number): string {\n // Handle non-finite values gracefully\n if (!Number.isFinite(seconds) || seconds < 0) {\n return \"0:00\";\n }\n const s = Math.floor(seconds);\n const m = Math.floor(s / 60);\n const sec = s % 60;\n return `${m}:${sec.toString().padStart(2, \"0\")}`;\n}\n\nexport function createControls(\n parent: ShadowRoot | HTMLElement,\n callbacks: ControlsCallbacks,\n options: ControlsOptions = {},\n): {\n updateTime: (current: number, duration: number) => void;\n updatePlaying: (playing: boolean) => void;\n updateSpeed: (speed: number) => void;\n show: () => void;\n hide: () => void;\n destroy: () => void;\n} {\n const presets = options.speedPresets ?? SPEED_PRESETS;\n\n const controls = document.createElement(\"div\");\n controls.className = \"hfp-controls\";\n // Keep overlay interactions from falling through to the host-level click toggle.\n controls.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n });\n\n const playBtn = document.createElement(\"button\");\n playBtn.className = \"hfp-play-btn\";\n playBtn.type = \"button\";\n playBtn.innerHTML = PLAY_ICON;\n playBtn.setAttribute(\"aria-label\", \"Play\");\n\n const scrubber = document.createElement(\"div\");\n scrubber.className = \"hfp-scrubber\";\n const progress = document.createElement(\"div\");\n progress.className = \"hfp-progress\";\n progress.style.width = \"0%\";\n scrubber.appendChild(progress);\n\n const time = document.createElement(\"span\");\n time.className = \"hfp-time\";\n time.textContent = \"0:00 / 0:00\";\n\n const speedWrap = document.createElement(\"div\");\n speedWrap.className = \"hfp-speed-wrap\";\n\n const speedBtn = document.createElement(\"button\");\n speedBtn.className = \"hfp-speed-btn\";\n speedBtn.type = \"button\";\n speedBtn.textContent = \"1x\";\n speedBtn.setAttribute(\"aria-label\", \"Playback speed\");\n\n const speedMenu = document.createElement(\"div\");\n speedMenu.className = \"hfp-speed-menu\";\n speedMenu.setAttribute(\"role\", \"menu\");\n for (const preset of presets) {\n const item = document.createElement(\"button\");\n item.className = \"hfp-speed-option\";\n item.type = \"button\";\n item.setAttribute(\"role\", \"menuitem\");\n item.dataset.speed = String(preset);\n item.textContent = formatSpeed(preset);\n if (preset === 1) item.classList.add(\"hfp-active\");\n speedMenu.appendChild(item);\n }\n\n speedWrap.appendChild(speedMenu);\n speedWrap.appendChild(speedBtn);\n\n controls.appendChild(playBtn);\n controls.appendChild(scrubber);\n controls.appendChild(time);\n controls.appendChild(speedWrap);\n parent.appendChild(controls);\n\n let isPlaying = false;\n let hideTimeout: ReturnType<typeof setTimeout> | null = null;\n let speedIndex = presets.indexOf(1); // start at 1x\n if (speedIndex === -1) speedIndex = 0;\n\n playBtn.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n if (isPlaying) callbacks.onPause();\n else callbacks.onPlay();\n });\n\n const setActiveOption = (speed: number) => {\n for (const opt of speedMenu.querySelectorAll(\".hfp-speed-option\")) {\n opt.classList.toggle(\"hfp-active\", (opt as HTMLElement).dataset.speed === String(speed));\n }\n };\n\n speedBtn.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n const isOpen = speedMenu.classList.toggle(\"hfp-open\");\n speedBtn.setAttribute(\"aria-expanded\", String(isOpen));\n });\n\n speedMenu.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n const target = (e.target as HTMLElement).closest(\".hfp-speed-option\") as HTMLElement | null;\n if (!target) return;\n const newSpeed = parseFloat(target.dataset.speed!);\n speedIndex = presets.indexOf(newSpeed);\n speedBtn.textContent = formatSpeed(newSpeed);\n setActiveOption(newSpeed);\n speedMenu.classList.remove(\"hfp-open\");\n speedBtn.setAttribute(\"aria-expanded\", \"false\");\n callbacks.onSpeedChange(newSpeed);\n });\n\n // Close menu when clicking outside\n const onDocClick = () => {\n speedMenu.classList.remove(\"hfp-open\");\n speedBtn.setAttribute(\"aria-expanded\", \"false\");\n };\n document.addEventListener(\"click\", onDocClick);\n\n const handleScrubAt = (clientX: number) => {\n const rect = scrubber.getBoundingClientRect();\n const fraction = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));\n callbacks.onSeek(fraction);\n };\n\n let scrubbing = false;\n\n scrubber.addEventListener(\"mousedown\", (e) => {\n e.stopPropagation();\n scrubbing = true;\n handleScrubAt(e.clientX);\n });\n const onMouseMove = (e: MouseEvent) => {\n if (scrubbing) handleScrubAt(e.clientX);\n };\n const onMouseUp = () => {\n scrubbing = false;\n };\n document.addEventListener(\"mousemove\", onMouseMove);\n document.addEventListener(\"mouseup\", onMouseUp);\n\n scrubber.addEventListener(\n \"touchstart\",\n (e) => {\n scrubbing = true;\n const touch = e.touches[0];\n if (touch) handleScrubAt(touch.clientX);\n },\n { passive: true },\n );\n const onTouchMove = (e: TouchEvent) => {\n if (scrubbing) {\n const touch = e.touches[0];\n if (touch) handleScrubAt(touch.clientX);\n }\n };\n const onTouchEnd = () => {\n scrubbing = false;\n };\n document.addEventListener(\"touchmove\", onTouchMove, { passive: true });\n document.addEventListener(\"touchend\", onTouchEnd);\n\n const startHideTimer = () => {\n if (hideTimeout) clearTimeout(hideTimeout);\n hideTimeout = setTimeout(() => {\n if (isPlaying) controls.classList.add(\"hfp-hidden\");\n }, 3000);\n };\n\n const host = parent instanceof ShadowRoot ? (parent.host as HTMLElement) : parent;\n host.addEventListener(\"mousemove\", () => {\n controls.classList.remove(\"hfp-hidden\");\n startHideTimer();\n });\n host.addEventListener(\"mouseleave\", () => {\n if (isPlaying) controls.classList.add(\"hfp-hidden\");\n });\n\n return {\n updateTime(current: number, duration: number) {\n const pct = duration > 0 ? (current / duration) * 100 : 0;\n progress.style.width = `${pct}%`;\n time.textContent = `${formatTime(current)} / ${formatTime(duration)}`;\n },\n updatePlaying(playing: boolean) {\n isPlaying = playing;\n playBtn.innerHTML = playing ? PAUSE_ICON : PLAY_ICON;\n playBtn.setAttribute(\"aria-label\", playing ? \"Pause\" : \"Play\");\n if (playing) startHideTimer();\n else controls.classList.remove(\"hfp-hidden\");\n },\n updateSpeed(speed: number) {\n const idx = presets.indexOf(speed);\n if (idx !== -1) speedIndex = idx;\n speedBtn.textContent = formatSpeed(speed);\n setActiveOption(speed);\n },\n show() {\n controls.style.display = \"\";\n },\n hide() {\n controls.style.display = \"none\";\n },\n destroy() {\n document.removeEventListener(\"mousemove\", onMouseMove);\n document.removeEventListener(\"mouseup\", onMouseUp);\n document.removeEventListener(\"touchmove\", onTouchMove);\n document.removeEventListener(\"touchend\", onTouchEnd);\n document.removeEventListener(\"click\", onDocClick);\n if (hideTimeout) clearTimeout(hideTimeout);\n },\n };\n}\n"],"mappings":"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","names":["hyperframes_player_exports","__export","HyperframesPlayer","SPEED_PRESETS","formatSpeed","formatTime","__toCommonJS","PLAYER_STYLES","PLAY_ICON","PAUSE_ICON","SPEED_PRESETS","formatSpeed","speed","formatTime","seconds","s","m","sec","createControls","parent","callbacks","options","presets","controls","e","playBtn","PLAY_ICON","scrubber","progress","time","speedWrap","speedBtn","speedMenu","preset","item","isPlaying","hideTimeout","speedIndex","setActiveOption","opt","isOpen","target","newSpeed","onDocClick","handleScrubAt","clientX","rect","fraction","scrubbing","onMouseMove","onMouseUp","touch","onTouchMove","onTouchEnd","startHideTimer","host","current","duration","pct","playing","PAUSE_ICON","idx","DEFAULT_FPS","RUNTIME_CDN_URL","HyperframesPlayer","style","PLAYER_STYLES","event","m","name","_old","val","rate","timeInSeconds","frame","relTime","t","r","l","action","extra","target","data","wasPlaying","now","attempts","win","hasRuntime","hasTimelines","adapter","keys","rootId","key","tl","root","w","h","doc","script","rect","scale","callbacks","fraction","speed","presetsAttr","speedPresets","n","createControls","url","mediaEls","el","src","tag","start","duration","audioSrc","iframeEl"]}
|
|
@@ -90,6 +90,7 @@ declare class HyperframesPlayer extends HTMLElement {
|
|
|
90
90
|
private _setupControls;
|
|
91
91
|
private _setupPoster;
|
|
92
92
|
private _playParentMedia;
|
|
93
|
+
private _muteIframeMedia;
|
|
93
94
|
private _pauseParentMedia;
|
|
94
95
|
/** Create a parent-frame media element, configure it, and start preloading. */
|
|
95
96
|
private _createParentMedia;
|
|
@@ -90,6 +90,7 @@ declare class HyperframesPlayer extends HTMLElement {
|
|
|
90
90
|
private _setupControls;
|
|
91
91
|
private _setupPoster;
|
|
92
92
|
private _playParentMedia;
|
|
93
|
+
private _muteIframeMedia;
|
|
93
94
|
private _pauseParentMedia;
|
|
94
95
|
/** Create a parent-frame media element, configure it, and start preloading. */
|
|
95
96
|
private _createParentMedia;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var HyperframesPlayer=(()=>{var w=Object.defineProperty;var F=Object.getOwnPropertyDescriptor;var j=Object.getOwnPropertyNames;var z=Object.prototype.hasOwnProperty;var U=(o,e)=>{for(var t in e)w(o,t,{get:e[t],enumerable:!0})},W=(o,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of j(e))!z.call(o,i)&&i!==t&&w(o,i,{get:()=>e[i],enumerable:!(s=F(e,i))||s.enumerable});return o};var $=o=>W(w({},"__esModule",{value:!0}),o);var q={};U(q,{HyperframesPlayer:()=>x,SPEED_PRESETS:()=>k,formatSpeed:()=>v,formatTime:()=>E});var
|
|
1
|
+
"use strict";var HyperframesPlayer=(()=>{var w=Object.defineProperty;var F=Object.getOwnPropertyDescriptor;var j=Object.getOwnPropertyNames;var z=Object.prototype.hasOwnProperty;var U=(o,e)=>{for(var t in e)w(o,t,{get:e[t],enumerable:!0})},W=(o,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of j(e))!z.call(o,i)&&i!==t&&w(o,i,{get:()=>e[i],enumerable:!(s=F(e,i))||s.enumerable});return o};var $=o=>W(w({},"__esModule",{value:!0}),o);var q={};U(q,{HyperframesPlayer:()=>x,SPEED_PRESETS:()=>k,formatSpeed:()=>v,formatTime:()=>E});var H=`
|
|
2
2
|
:host {
|
|
3
3
|
display: block;
|
|
4
4
|
position: relative;
|
|
@@ -195,5 +195,5 @@
|
|
|
195
195
|
color: var(--hfp-accent, #fff);
|
|
196
196
|
font-weight: 600;
|
|
197
197
|
}
|
|
198
|
-
`,C='<svg width="24" height="24" viewBox="0 0 18 18" fill="currentColor"><polygon points="4,2 16,9 4,16"/></svg>',H='<svg width="24" height="24" viewBox="0 0 18 18" fill="currentColor"><rect x="3" y="2" width="4" height="14"/><rect x="11" y="2" width="4" height="14"/></svg>';var k=[.25,.5,1,1.5,2,4];function v(o){return Number.isInteger(o)?`${o}x`:`${o}x`}function E(o){if(!Number.isFinite(o)||o<0)return"0:00";let e=Math.floor(o),t=Math.floor(e/60),s=e%60;return`${t}:${s.toString().padStart(2,"0")}`}function D(o,e,t={}){let s=t.speedPresets??k,i=document.createElement("div");i.className="hfp-controls",i.addEventListener("click",r=>{r.stopPropagation()});let a=document.createElement("button");a.className="hfp-play-btn",a.type="button",a.innerHTML=C,a.setAttribute("aria-label","Play");let d=document.createElement("div");d.className="hfp-scrubber";let l=document.createElement("div");l.className="hfp-progress",l.style.width="0%",d.appendChild(l);let c=document.createElement("span");c.className="hfp-time",c.textContent="0:00 / 0:00";let h=document.createElement("div");h.className="hfp-speed-wrap";let p=document.createElement("button");p.className="hfp-speed-btn",p.type="button",p.textContent="1x",p.setAttribute("aria-label","Playback speed");let u=document.createElement("div");u.className="hfp-speed-menu",u.setAttribute("role","menu");for(let r of s){let n=document.createElement("button");n.className="hfp-speed-option",n.type="button",n.setAttribute("role","menuitem"),n.dataset.speed=String(r),n.textContent=v(r),r===1&&n.classList.add("hfp-active"),u.appendChild(n)}h.appendChild(u),h.appendChild(p),i.appendChild(a),i.appendChild(d),i.appendChild(c),i.appendChild(h),o.appendChild(i);let g=!1,b=null,_=s.indexOf(1);_===-1&&(_=0),a.addEventListener("click",r=>{r.stopPropagation(),g?e.onPause():e.onPlay()});let M=r=>{for(let n of u.querySelectorAll(".hfp-speed-option"))n.classList.toggle("hfp-active",n.dataset.speed===String(r))};p.addEventListener("click",r=>{r.stopPropagation();let n=u.classList.toggle("hfp-open");p.setAttribute("aria-expanded",String(n))}),u.addEventListener("click",r=>{r.stopPropagation();let n=r.target.closest(".hfp-speed-option");if(!n)return;let m=parseFloat(n.dataset.speed);_=s.indexOf(m),p.textContent=v(m),M(m),u.classList.remove("hfp-open"),p.setAttribute("aria-expanded","false"),e.onSpeedChange(m)});let P=()=>{u.classList.remove("hfp-open"),p.setAttribute("aria-expanded","false")};document.addEventListener("click",P);let y=r=>{let n=d.getBoundingClientRect(),m=Math.max(0,Math.min(1,(r-n.left)/n.width));e.onSeek(m)},f=!1;d.addEventListener("mousedown",r=>{r.stopPropagation(),f=!0,y(r.clientX)});let L=r=>{f&&y(r.clientX)},T=()=>{f=!1};document.addEventListener("mousemove",L),document.addEventListener("mouseup",T),d.addEventListener("touchstart",r=>{f=!0;let n=r.touches[0];n&&y(n.clientX)},{passive:!0});let S=r=>{if(f){let n=r.touches[0];n&&y(n.clientX)}},I=()=>{f=!1};document.addEventListener("touchmove",S,{passive:!0}),document.addEventListener("touchend",I);let R=()=>{b&&clearTimeout(b),b=setTimeout(()=>{g&&i.classList.add("hfp-hidden")},3e3)},N=o instanceof ShadowRoot?o.host:o;return N.addEventListener("mousemove",()=>{i.classList.remove("hfp-hidden"),R()}),N.addEventListener("mouseleave",()=>{g&&i.classList.add("hfp-hidden")}),{updateTime(r,n){let m=n>0?r/n*100:0;l.style.width=`${m}%`,c.textContent=`${E(r)} / ${E(n)}`},updatePlaying(r){g=r,a.innerHTML=r?H:C,a.setAttribute("aria-label",r?"Pause":"Play"),r?R():i.classList.remove("hfp-hidden")},updateSpeed(r){let n=s.indexOf(r);n!==-1&&(_=n),p.textContent=v(r),M(r)},show(){i.style.display=""},hide(){i.style.display="none"},destroy(){document.removeEventListener("mousemove",L),document.removeEventListener("mouseup",T),document.removeEventListener("touchmove",S),document.removeEventListener("touchend",I),document.removeEventListener("click",P),b&&clearTimeout(b)}}}var A=30,Y="https://cdn.jsdelivr.net/npm/@hyperframes/core/dist/hyperframe.runtime.iife.js",x=class extends HTMLElement{static get observedAttributes(){return["src","width","height","controls","muted","poster","playback-rate","audio-src"]}shadow;container;iframe;posterEl=null;controlsApi=null;resizeObserver;_ready=!1;_duration=0;_currentTime=0;_paused=!0;_compositionWidth=1920;_compositionHeight=1080;_probeInterval=null;_lastUpdateMs=0;_parentMedia=[];constructor(){super(),this.shadow=this.attachShadow({mode:"open"});let e=document.createElement("style");e.textContent=O,this.shadow.appendChild(e),this.container=document.createElement("div"),this.container.className="hfp-container",this.iframe=document.createElement("iframe"),this.iframe.className="hfp-iframe",this.iframe.sandbox.add("allow-scripts","allow-same-origin"),this.iframe.allow="autoplay; fullscreen",this.iframe.referrerPolicy="no-referrer",this.iframe.title="HyperFrames Composition",this.container.appendChild(this.iframe),this.shadow.appendChild(this.container),this.addEventListener("click",t=>{this._isControlsClick(t)||(this._paused?this.play():this.pause())}),this.resizeObserver=new ResizeObserver(()=>this._updateScale()),this._onMessage=this._onMessage.bind(this),this._onIframeLoad=this._onIframeLoad.bind(this)}connectedCallback(){this.resizeObserver.observe(this),window.addEventListener("message",this._onMessage),this.iframe.addEventListener("load",this._onIframeLoad),this.hasAttribute("controls")&&this._setupControls(),this.hasAttribute("poster")&&this._setupPoster(),this.hasAttribute("audio-src")&&this._setupParentAudioFromUrl(this.getAttribute("audio-src")),this.hasAttribute("src")&&(this.iframe.src=this.getAttribute("src"))}disconnectedCallback(){this.resizeObserver.disconnect(),window.removeEventListener("message",this._onMessage),this.iframe.removeEventListener("load",this._onIframeLoad),this._probeInterval&&clearInterval(this._probeInterval),this.controlsApi?.destroy();for(let e of this._parentMedia)e.el.pause(),e.el.src="";this._parentMedia=[]}attributeChangedCallback(e,t,s){switch(e){case"src":s&&(this._ready=!1,this.iframe.src=s);break;case"width":this._compositionWidth=parseInt(s||"1920",10),this._updateScale();break;case"height":this._compositionHeight=parseInt(s||"1080",10),this._updateScale();break;case"controls":s!==null?this._setupControls():(this.controlsApi?.destroy(),this.controlsApi=null);break;case"poster":this._setupPoster();break;case"playback-rate":{let i=parseFloat(s||"1");for(let a of this._parentMedia)a.el.playbackRate=i;this._sendControl("set-playback-rate",{playbackRate:i}),this.controlsApi?.updateSpeed(i),this.dispatchEvent(new Event("ratechange"));break}case"muted":for(let i of this._parentMedia)i.el.muted=s!==null;this._sendControl("set-muted",{muted:s!==null});break;case"audio-src":s&&this._setupParentAudioFromUrl(s);break}}get iframeElement(){return this.iframe}play(){this._hidePoster(),this._playParentMedia(),this._sendControl("play"),this._paused=!1,this.controlsApi?.updatePlaying(!0),this.dispatchEvent(new Event("play"))}pause(){this._pauseParentMedia(),this._sendControl("pause"),this._paused=!0,this.controlsApi?.updatePlaying(!1),this.dispatchEvent(new Event("pause"))}seek(e){let t=Math.round(e*A);this._sendControl("seek",{frame:t}),this._currentTime=e;for(let s of this._parentMedia){let i=e-s.start;i>=0&&i<s.duration&&(s.el.currentTime=i)}this._paused=!0,this.controlsApi?.updatePlaying(!1),this.controlsApi?.updateTime(this._currentTime,this._duration)}get currentTime(){return this._currentTime}set currentTime(e){this.seek(e)}get duration(){return this._duration}get paused(){return this._paused}get ready(){return this._ready}get playbackRate(){return parseFloat(this.getAttribute("playback-rate")||"1")}set playbackRate(e){this.setAttribute("playback-rate",String(e))}get muted(){return this.hasAttribute("muted")}set muted(e){e?this.setAttribute("muted",""):this.removeAttribute("muted")}get loop(){return this.hasAttribute("loop")}set loop(e){e?this.setAttribute("loop",""):this.removeAttribute("loop")}_sendControl(e,t={}){try{this.iframe.contentWindow?.postMessage({source:"hf-parent",type:"control",action:e,...t},"*")}catch{}}_isControlsClick(e){return e.composedPath().some(t=>t instanceof HTMLElement&&t.classList.contains("hfp-controls"))}_onMessage(e){if(e.source!==this.iframe.contentWindow)return;let t=e.data;if(!(!t||t.source!=="hf-preview")){if(t.type==="state"){this._currentTime=(t.frame??0)/A;let s=!this._paused;this._paused=!t.isPlaying,s&&this._paused?this._pauseParentMedia():!s&&!this._paused&&this._playParentMedia();let i=performance.now();(i-this._lastUpdateMs>100||this._paused!==s)&&(this._lastUpdateMs=i,this.controlsApi?.updateTime(this._currentTime,this._duration),this.controlsApi?.updatePlaying(!this._paused),this.dispatchEvent(new CustomEvent("timeupdate",{detail:{currentTime:this._currentTime}}))),this._currentTime>=this._duration&&!this._paused&&(this._pauseParentMedia(),this.loop?(this.seek(0),this.play()):(this._paused=!0,this.controlsApi?.updatePlaying(!1),this.dispatchEvent(new Event("ended"))))}t.type==="timeline"&&t.durationInFrames>0&&Number.isFinite(t.durationInFrames)&&(this._duration=t.durationInFrames/A,this.controlsApi?.updateTime(this._currentTime,this._duration)),t.type==="stage-size"&&t.width>0&&t.height>0&&(this._compositionWidth=t.width,this._compositionHeight=t.height,this._updateScale())}}_runtimeInjected=!1;_onIframeLoad(){let e=0;this._runtimeInjected=!1,this._probeInterval&&clearInterval(this._probeInterval),this._probeInterval=setInterval(()=>{e++;try{let t=this.iframe.contentWindow;if(!t)return;let s=!!(t.__hf||t.__player),i=!!(t.__timelines&&Object.keys(t.__timelines).length>0);if(!s&&i&&!this._runtimeInjected&&e>=5){this._injectRuntime();return}if(this._runtimeInjected&&!s)return;let d=(()=>{if(t.__player&&typeof t.__player.getDuration=="function")return t.__player;if(t.__timelines){let l=Object.keys(t.__timelines);if(l.length>0){let c=this.iframe.contentDocument?.querySelector("[data-composition-id]")?.getAttribute("data-composition-id"),h=c&&c in t.__timelines?c:l[l.length-1],p=t.__timelines[h];return{getDuration:()=>p.duration()}}}return null})();if(d&&d.getDuration()>0){clearInterval(this._probeInterval),this._duration=d.getDuration(),this._ready=!0,this.controlsApi?.updateTime(0,this._duration),this.dispatchEvent(new CustomEvent("ready",{detail:{duration:this._duration}}));let c=this.iframe.contentDocument?.querySelector("[data-composition-id]");if(c){let h=parseInt(c.getAttribute("data-width")||"0",10),p=parseInt(c.getAttribute("data-height")||"0",10);h>0&&p>0&&(this._compositionWidth=h,this._compositionHeight=p,this._updateScale())}this._setupParentMedia(),this.hasAttribute("autoplay")&&this.play();return}}catch{}e>=40&&(clearInterval(this._probeInterval),this.dispatchEvent(new CustomEvent("error",{detail:{message:"Composition timeline not found after 8s"}})))},200)}_injectRuntime(){this._runtimeInjected=!0;try{let e=this.iframe.contentDocument;if(!e)return;let t=e.createElement("script");t.src=Y,t.onload=()=>{},t.onerror=()=>{},(e.head||e.documentElement).appendChild(t)}catch{}}_updateScale(){let e=this.getBoundingClientRect();if(e.width===0||e.height===0)return;let t=Math.min(e.width/this._compositionWidth,e.height/this._compositionHeight);this.iframe.style.width=`${this._compositionWidth}px`,this.iframe.style.height=`${this._compositionHeight}px`,this.iframe.style.transform=`translate(-50%, -50%) scale(${t})`}_setupControls(){if(this.controlsApi)return;let e={onPlay:()=>this.play(),onPause:()=>this.pause(),onSeek:i=>this.seek(i*this._duration),onSpeedChange:i=>{this.playbackRate=i}},t=this.getAttribute("speed-presets"),s=t?t.split(",").map(Number).filter(i=>!isNaN(i)&&i>0):void 0;this.controlsApi=D(this.shadow,e,{speedPresets:s})}_setupPoster(){let e=this.getAttribute("poster");if(!e){this.posterEl?.remove(),this.posterEl=null;return}this.posterEl||(this.posterEl=document.createElement("img"),this.posterEl.className="hfp-poster",this.shadow.appendChild(this.posterEl)),this.posterEl.src=e}_playParentMedia(){for(let e of this._parentMedia)e.el.src&&e.el.play().catch(()=>{})}_pauseParentMedia(){for(let e of this._parentMedia)e.el.pause()}_createParentMedia(e,t,s,i){if(this._parentMedia.some(d=>d.el.src===e))return;let a=t==="video"?document.createElement("video"):new Audio;a.preload="auto",a.src=e,a.load(),a.muted=this.muted,this.playbackRate!==1&&(a.playbackRate=this.playbackRate),this._parentMedia.push({el:a,start:s,duration:i})}_setupParentAudioFromUrl(e){this._createParentMedia(e,"audio",0,1/0)}_setupParentMedia(){try{let e=this.iframe.contentDocument;if(!e)return;let t=e.querySelectorAll("audio[data-start], video[data-start]");for(let s of t){let i=s.getAttribute("src")||s.querySelector("source")?.src;if(!i)continue;let a=parseFloat(s.getAttribute("data-start")||"0"),d=parseFloat(s.getAttribute("data-duration")||"Infinity"),l=s.tagName==="VIDEO"?"video":"audio";this._createParentMedia(i,l,a,d),s.removeAttribute("src"),s.removeAttribute("data-start"),s.removeAttribute("data-duration"),s.querySelectorAll("source").forEach(c=>c.remove())}}catch{}}_hidePoster(){this.posterEl?.remove(),this.posterEl=null}};customElements.get("hyperframes-player")||customElements.define("hyperframes-player",x);return $(q);})();
|
|
198
|
+
`,C='<svg width="24" height="24" viewBox="0 0 18 18" fill="currentColor"><polygon points="4,2 16,9 4,16"/></svg>',O='<svg width="24" height="24" viewBox="0 0 18 18" fill="currentColor"><rect x="3" y="2" width="4" height="14"/><rect x="11" y="2" width="4" height="14"/></svg>';var k=[.25,.5,1,1.5,2,4];function v(o){return Number.isInteger(o)?`${o}x`:`${o}x`}function E(o){if(!Number.isFinite(o)||o<0)return"0:00";let e=Math.floor(o),t=Math.floor(e/60),s=e%60;return`${t}:${s.toString().padStart(2,"0")}`}function D(o,e,t={}){let s=t.speedPresets??k,i=document.createElement("div");i.className="hfp-controls",i.addEventListener("click",r=>{r.stopPropagation()});let a=document.createElement("button");a.className="hfp-play-btn",a.type="button",a.innerHTML=C,a.setAttribute("aria-label","Play");let d=document.createElement("div");d.className="hfp-scrubber";let l=document.createElement("div");l.className="hfp-progress",l.style.width="0%",d.appendChild(l);let c=document.createElement("span");c.className="hfp-time",c.textContent="0:00 / 0:00";let h=document.createElement("div");h.className="hfp-speed-wrap";let p=document.createElement("button");p.className="hfp-speed-btn",p.type="button",p.textContent="1x",p.setAttribute("aria-label","Playback speed");let u=document.createElement("div");u.className="hfp-speed-menu",u.setAttribute("role","menu");for(let r of s){let n=document.createElement("button");n.className="hfp-speed-option",n.type="button",n.setAttribute("role","menuitem"),n.dataset.speed=String(r),n.textContent=v(r),r===1&&n.classList.add("hfp-active"),u.appendChild(n)}h.appendChild(u),h.appendChild(p),i.appendChild(a),i.appendChild(d),i.appendChild(c),i.appendChild(h),o.appendChild(i);let g=!1,b=null,_=s.indexOf(1);_===-1&&(_=0),a.addEventListener("click",r=>{r.stopPropagation(),g?e.onPause():e.onPlay()});let M=r=>{for(let n of u.querySelectorAll(".hfp-speed-option"))n.classList.toggle("hfp-active",n.dataset.speed===String(r))};p.addEventListener("click",r=>{r.stopPropagation();let n=u.classList.toggle("hfp-open");p.setAttribute("aria-expanded",String(n))}),u.addEventListener("click",r=>{r.stopPropagation();let n=r.target.closest(".hfp-speed-option");if(!n)return;let m=parseFloat(n.dataset.speed);_=s.indexOf(m),p.textContent=v(m),M(m),u.classList.remove("hfp-open"),p.setAttribute("aria-expanded","false"),e.onSpeedChange(m)});let L=()=>{u.classList.remove("hfp-open"),p.setAttribute("aria-expanded","false")};document.addEventListener("click",L);let y=r=>{let n=d.getBoundingClientRect(),m=Math.max(0,Math.min(1,(r-n.left)/n.width));e.onSeek(m)},f=!1;d.addEventListener("mousedown",r=>{r.stopPropagation(),f=!0,y(r.clientX)});let P=r=>{f&&y(r.clientX)},T=()=>{f=!1};document.addEventListener("mousemove",P),document.addEventListener("mouseup",T),d.addEventListener("touchstart",r=>{f=!0;let n=r.touches[0];n&&y(n.clientX)},{passive:!0});let S=r=>{if(f){let n=r.touches[0];n&&y(n.clientX)}},I=()=>{f=!1};document.addEventListener("touchmove",S,{passive:!0}),document.addEventListener("touchend",I);let R=()=>{b&&clearTimeout(b),b=setTimeout(()=>{g&&i.classList.add("hfp-hidden")},3e3)},N=o instanceof ShadowRoot?o.host:o;return N.addEventListener("mousemove",()=>{i.classList.remove("hfp-hidden"),R()}),N.addEventListener("mouseleave",()=>{g&&i.classList.add("hfp-hidden")}),{updateTime(r,n){let m=n>0?r/n*100:0;l.style.width=`${m}%`,c.textContent=`${E(r)} / ${E(n)}`},updatePlaying(r){g=r,a.innerHTML=r?O:C,a.setAttribute("aria-label",r?"Pause":"Play"),r?R():i.classList.remove("hfp-hidden")},updateSpeed(r){let n=s.indexOf(r);n!==-1&&(_=n),p.textContent=v(r),M(r)},show(){i.style.display=""},hide(){i.style.display="none"},destroy(){document.removeEventListener("mousemove",P),document.removeEventListener("mouseup",T),document.removeEventListener("touchmove",S),document.removeEventListener("touchend",I),document.removeEventListener("click",L),b&&clearTimeout(b)}}}var A=30,Y="https://cdn.jsdelivr.net/npm/@hyperframes/core/dist/hyperframe.runtime.iife.js",x=class extends HTMLElement{static get observedAttributes(){return["src","width","height","controls","muted","poster","playback-rate","audio-src"]}shadow;container;iframe;posterEl=null;controlsApi=null;resizeObserver;_ready=!1;_duration=0;_currentTime=0;_paused=!0;_compositionWidth=1920;_compositionHeight=1080;_probeInterval=null;_lastUpdateMs=0;_parentMedia=[];constructor(){super(),this.shadow=this.attachShadow({mode:"open"});let e=document.createElement("style");e.textContent=H,this.shadow.appendChild(e),this.container=document.createElement("div"),this.container.className="hfp-container",this.iframe=document.createElement("iframe"),this.iframe.className="hfp-iframe",this.iframe.sandbox.add("allow-scripts","allow-same-origin"),this.iframe.allow="autoplay; fullscreen",this.iframe.referrerPolicy="no-referrer",this.iframe.title="HyperFrames Composition",this.container.appendChild(this.iframe),this.shadow.appendChild(this.container),this.addEventListener("click",t=>{this._isControlsClick(t)||(this._paused?this.play():this.pause())}),this.resizeObserver=new ResizeObserver(()=>this._updateScale()),this._onMessage=this._onMessage.bind(this),this._onIframeLoad=this._onIframeLoad.bind(this)}connectedCallback(){this.resizeObserver.observe(this),window.addEventListener("message",this._onMessage),this.iframe.addEventListener("load",this._onIframeLoad),this.hasAttribute("controls")&&this._setupControls(),this.hasAttribute("poster")&&this._setupPoster(),this.hasAttribute("audio-src")&&this._setupParentAudioFromUrl(this.getAttribute("audio-src")),this.hasAttribute("src")&&(this.iframe.src=this.getAttribute("src"))}disconnectedCallback(){this.resizeObserver.disconnect(),window.removeEventListener("message",this._onMessage),this.iframe.removeEventListener("load",this._onIframeLoad),this._probeInterval&&clearInterval(this._probeInterval),this.controlsApi?.destroy();for(let e of this._parentMedia)e.el.pause(),e.el.src="";this._parentMedia=[]}attributeChangedCallback(e,t,s){switch(e){case"src":s&&(this._ready=!1,this.iframe.src=s);break;case"width":this._compositionWidth=parseInt(s||"1920",10),this._updateScale();break;case"height":this._compositionHeight=parseInt(s||"1080",10),this._updateScale();break;case"controls":s!==null?this._setupControls():(this.controlsApi?.destroy(),this.controlsApi=null);break;case"poster":this._setupPoster();break;case"playback-rate":{let i=parseFloat(s||"1");for(let a of this._parentMedia)a.el.playbackRate=i;this._sendControl("set-playback-rate",{playbackRate:i}),this.controlsApi?.updateSpeed(i),this.dispatchEvent(new Event("ratechange"));break}case"muted":for(let i of this._parentMedia)i.el.muted=s!==null;this._sendControl("set-muted",{muted:s!==null});break;case"audio-src":s&&this._setupParentAudioFromUrl(s);break}}get iframeElement(){return this.iframe}play(){this._hidePoster(),this._playParentMedia(),this._sendControl("play"),this._paused=!1,this.controlsApi?.updatePlaying(!0),this.dispatchEvent(new Event("play"))}pause(){this._pauseParentMedia(),this._sendControl("pause"),this._paused=!0,this.controlsApi?.updatePlaying(!1),this.dispatchEvent(new Event("pause"))}seek(e){let t=Math.round(e*A);this._sendControl("seek",{frame:t}),this._currentTime=e;for(let s of this._parentMedia){let i=e-s.start;i>=0&&i<s.duration&&(s.el.currentTime=i)}this._paused=!0,this.controlsApi?.updatePlaying(!1),this.controlsApi?.updateTime(this._currentTime,this._duration)}get currentTime(){return this._currentTime}set currentTime(e){this.seek(e)}get duration(){return this._duration}get paused(){return this._paused}get ready(){return this._ready}get playbackRate(){return parseFloat(this.getAttribute("playback-rate")||"1")}set playbackRate(e){this.setAttribute("playback-rate",String(e))}get muted(){return this.hasAttribute("muted")}set muted(e){e?this.setAttribute("muted",""):this.removeAttribute("muted")}get loop(){return this.hasAttribute("loop")}set loop(e){e?this.setAttribute("loop",""):this.removeAttribute("loop")}_sendControl(e,t={}){try{this.iframe.contentWindow?.postMessage({source:"hf-parent",type:"control",action:e,...t},"*")}catch{}}_isControlsClick(e){return e.composedPath().some(t=>t instanceof HTMLElement&&t.classList.contains("hfp-controls"))}_onMessage(e){if(e.source!==this.iframe.contentWindow)return;let t=e.data;if(!(!t||t.source!=="hf-preview")){if(t.type==="state"){this._currentTime=(t.frame??0)/A;let s=!this._paused;this._paused=!t.isPlaying,s&&this._paused?this._pauseParentMedia():!s&&!this._paused&&this._playParentMedia();let i=performance.now();(i-this._lastUpdateMs>100||this._paused!==s)&&(this._lastUpdateMs=i,this.controlsApi?.updateTime(this._currentTime,this._duration),this.controlsApi?.updatePlaying(!this._paused),this.dispatchEvent(new CustomEvent("timeupdate",{detail:{currentTime:this._currentTime}}))),this._currentTime>=this._duration&&!this._paused&&(this._pauseParentMedia(),this.loop?(this.seek(0),this.play()):(this._paused=!0,this.controlsApi?.updatePlaying(!1),this.dispatchEvent(new Event("ended"))))}t.type==="timeline"&&t.durationInFrames>0&&Number.isFinite(t.durationInFrames)&&(this._duration=t.durationInFrames/A,this.controlsApi?.updateTime(this._currentTime,this._duration)),t.type==="stage-size"&&t.width>0&&t.height>0&&(this._compositionWidth=t.width,this._compositionHeight=t.height,this._updateScale())}}_runtimeInjected=!1;_onIframeLoad(){let e=0;this._runtimeInjected=!1,this._probeInterval&&clearInterval(this._probeInterval),this._probeInterval=setInterval(()=>{e++;try{let t=this.iframe.contentWindow;if(!t)return;let s=!!(t.__hf||t.__player),i=!!(t.__timelines&&Object.keys(t.__timelines).length>0);if(!s&&i&&!this._runtimeInjected&&e>=5){this._injectRuntime();return}if(this._runtimeInjected&&!s)return;let d=(()=>{if(t.__player&&typeof t.__player.getDuration=="function")return t.__player;if(t.__timelines){let l=Object.keys(t.__timelines);if(l.length>0){let c=this.iframe.contentDocument?.querySelector("[data-composition-id]")?.getAttribute("data-composition-id"),h=c&&c in t.__timelines?c:l[l.length-1],p=t.__timelines[h];return{getDuration:()=>p.duration()}}}return null})();if(d&&d.getDuration()>0){clearInterval(this._probeInterval),this._duration=d.getDuration(),this._ready=!0,this.controlsApi?.updateTime(0,this._duration),this.dispatchEvent(new CustomEvent("ready",{detail:{duration:this._duration}}));let c=this.iframe.contentDocument?.querySelector("[data-composition-id]");if(c){let h=parseInt(c.getAttribute("data-width")||"0",10),p=parseInt(c.getAttribute("data-height")||"0",10);h>0&&p>0&&(this._compositionWidth=h,this._compositionHeight=p,this._updateScale())}this._setupParentMedia(),this.hasAttribute("autoplay")&&this.play();return}}catch{}e>=40&&(clearInterval(this._probeInterval),this.dispatchEvent(new CustomEvent("error",{detail:{message:"Composition timeline not found after 8s"}})))},200)}_injectRuntime(){this._runtimeInjected=!0;try{let e=this.iframe.contentDocument;if(!e)return;let t=e.createElement("script");t.src=Y,t.onload=()=>{},t.onerror=()=>{},(e.head||e.documentElement).appendChild(t)}catch{}}_updateScale(){let e=this.getBoundingClientRect();if(e.width===0||e.height===0)return;let t=Math.min(e.width/this._compositionWidth,e.height/this._compositionHeight);this.iframe.style.width=`${this._compositionWidth}px`,this.iframe.style.height=`${this._compositionHeight}px`,this.iframe.style.transform=`translate(-50%, -50%) scale(${t})`}_setupControls(){if(this.controlsApi)return;let e={onPlay:()=>this.play(),onPause:()=>this.pause(),onSeek:i=>this.seek(i*this._duration),onSpeedChange:i=>{this.playbackRate=i}},t=this.getAttribute("speed-presets"),s=t?t.split(",").map(Number).filter(i=>!isNaN(i)&&i>0):void 0;this.controlsApi=D(this.shadow,e,{speedPresets:s})}_setupPoster(){let e=this.getAttribute("poster");if(!e){this.posterEl?.remove(),this.posterEl=null;return}this.posterEl||(this.posterEl=document.createElement("img"),this.posterEl.className="hfp-poster",this.shadow.appendChild(this.posterEl)),this.posterEl.src=e}_playParentMedia(){for(let e of this._parentMedia)e.el.src&&e.el.play().then(()=>{this._muteIframeMedia()}).catch(()=>{})}_muteIframeMedia(){try{let e=this.iframe.contentDocument;if(!e)return;let t=e.querySelectorAll("audio[data-start], video[data-start]");for(let s of t)s.volume=0}catch{}}_pauseParentMedia(){for(let e of this._parentMedia)e.el.pause()}_createParentMedia(e,t,s,i){if(this._parentMedia.some(d=>d.el.src===e))return;let a=t==="video"?document.createElement("video"):new Audio;a.preload="auto",a.src=e,a.load(),a.muted=this.muted,this.playbackRate!==1&&(a.playbackRate=this.playbackRate),this._parentMedia.push({el:a,start:s,duration:i})}_setupParentAudioFromUrl(e){this._createParentMedia(e,"audio",0,1/0)}_setupParentMedia(){try{let e=this.iframe.contentDocument;if(!e)return;let t=e.querySelectorAll("audio[data-start], video[data-start]");for(let s of t){let i=s.getAttribute("src")||s.querySelector("source")?.src;if(!i)continue;let a=parseFloat(s.getAttribute("data-start")||"0"),d=parseFloat(s.getAttribute("data-duration")||"Infinity"),l=s.tagName==="VIDEO"?"video":"audio";this._createParentMedia(i,l,a,d)}}catch{}}_hidePoster(){this.posterEl?.remove(),this.posterEl=null}};customElements.get("hyperframes-player")||customElements.define("hyperframes-player",x);return $(q);})();
|
|
199
199
|
//# sourceMappingURL=hyperframes-player.global.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hyperframes-player.ts","../src/styles.ts","../src/controls.ts"],"sourcesContent":["import { createControls, SPEED_PRESETS, type ControlsCallbacks } from \"./controls.js\";\nimport { PLAYER_STYLES } from \"./styles.js\";\n\nconst DEFAULT_FPS = 30;\nconst RUNTIME_CDN_URL =\n \"https://cdn.jsdelivr.net/npm/@hyperframes/core/dist/hyperframe.runtime.iife.js\";\n\nclass HyperframesPlayer extends HTMLElement {\n static get observedAttributes() {\n return [\"src\", \"width\", \"height\", \"controls\", \"muted\", \"poster\", \"playback-rate\", \"audio-src\"];\n }\n\n private shadow: ShadowRoot;\n private container: HTMLDivElement;\n private iframe: HTMLIFrameElement;\n private posterEl: HTMLImageElement | null = null;\n private controlsApi: ReturnType<typeof createControls> | null = null;\n private resizeObserver: ResizeObserver;\n\n private _ready = false;\n private _duration = 0;\n private _currentTime = 0;\n private _paused = true;\n private _compositionWidth = 1920;\n private _compositionHeight = 1080;\n private _probeInterval: ReturnType<typeof setInterval> | null = null;\n private _lastUpdateMs = 0;\n\n /**\n * Parent-frame media elements for mobile playback.\n *\n * Mobile browsers block media.play() inside iframes when the user gesture\n * happened in the parent frame — postMessage doesn't transfer user activation\n * (per the User Activation v2 spec). We extract ALL media sources from the\n * iframe's timed elements (audio/video with data-start), play them in the\n * parent frame (where the gesture lives), and disable the iframe copies.\n */\n private _parentMedia: Array<{\n el: HTMLMediaElement;\n start: number;\n duration: number;\n }> = [];\n\n constructor() {\n super();\n this.shadow = this.attachShadow({ mode: \"open\" });\n\n const style = document.createElement(\"style\");\n style.textContent = PLAYER_STYLES;\n this.shadow.appendChild(style);\n\n this.container = document.createElement(\"div\");\n this.container.className = \"hfp-container\";\n\n this.iframe = document.createElement(\"iframe\");\n this.iframe.className = \"hfp-iframe\";\n this.iframe.sandbox.add(\"allow-scripts\", \"allow-same-origin\");\n this.iframe.allow = \"autoplay; fullscreen\";\n this.iframe.referrerPolicy = \"no-referrer\";\n this.iframe.title = \"HyperFrames Composition\";\n\n this.container.appendChild(this.iframe);\n this.shadow.appendChild(this.container);\n\n // Clicking the bare player surface toggles play/pause.\n // Ignore shadow-DOM control interactions so overlay clicks don't double-handle.\n this.addEventListener(\"click\", (event) => {\n if (this._isControlsClick(event)) return;\n if (this._paused) this.play();\n else this.pause();\n });\n\n this.resizeObserver = new ResizeObserver(() => this._updateScale());\n\n this._onMessage = this._onMessage.bind(this);\n this._onIframeLoad = this._onIframeLoad.bind(this);\n }\n\n connectedCallback() {\n this.resizeObserver.observe(this);\n window.addEventListener(\"message\", this._onMessage);\n this.iframe.addEventListener(\"load\", this._onIframeLoad);\n\n if (this.hasAttribute(\"controls\")) this._setupControls();\n if (this.hasAttribute(\"poster\")) this._setupPoster();\n if (this.hasAttribute(\"audio-src\"))\n this._setupParentAudioFromUrl(this.getAttribute(\"audio-src\")!);\n if (this.hasAttribute(\"src\")) this.iframe.src = this.getAttribute(\"src\")!;\n }\n\n disconnectedCallback() {\n this.resizeObserver.disconnect();\n window.removeEventListener(\"message\", this._onMessage);\n this.iframe.removeEventListener(\"load\", this._onIframeLoad);\n if (this._probeInterval) clearInterval(this._probeInterval);\n this.controlsApi?.destroy();\n for (const m of this._parentMedia) {\n m.el.pause();\n m.el.src = \"\";\n }\n this._parentMedia = [];\n }\n\n attributeChangedCallback(name: string, _old: string | null, val: string | null) {\n switch (name) {\n case \"src\":\n if (val) {\n this._ready = false;\n this.iframe.src = val;\n }\n break;\n case \"width\":\n this._compositionWidth = parseInt(val || \"1920\", 10);\n this._updateScale();\n break;\n case \"height\":\n this._compositionHeight = parseInt(val || \"1080\", 10);\n this._updateScale();\n break;\n case \"controls\":\n if (val !== null) this._setupControls();\n else {\n this.controlsApi?.destroy();\n this.controlsApi = null;\n }\n break;\n case \"poster\":\n this._setupPoster();\n break;\n case \"playback-rate\": {\n const rate = parseFloat(val || \"1\");\n for (const m of this._parentMedia) m.el.playbackRate = rate;\n this._sendControl(\"set-playback-rate\", { playbackRate: rate });\n this.controlsApi?.updateSpeed(rate);\n this.dispatchEvent(new Event(\"ratechange\"));\n break;\n }\n case \"muted\":\n for (const m of this._parentMedia) m.el.muted = val !== null;\n this._sendControl(\"set-muted\", { muted: val !== null });\n break;\n case \"audio-src\":\n if (val) this._setupParentAudioFromUrl(val);\n break;\n }\n }\n\n // ── Public API ──\n\n /**\n * Access the inner `<iframe>` element rendering the composition.\n *\n * Use this when integrating the player with editors, recorders, or\n * timeline tools (e.g. `@hyperframes/studio`) that need to inspect\n * the composition's DOM or read its `__player` / `__timelines`\n * runtime objects.\n *\n * **Common pitfall:** the iframe lives inside the player's Shadow DOM.\n * Passing the `<hyperframes-player>` element itself to code that expects\n * an `<iframe>` will silently break — `.contentWindow` returns `null`.\n * Always extract `iframeElement` first:\n *\n * ```ts\n * // ❌ Wrong — element ref doesn't expose contentWindow\n * iframeRef.current = playerRef.current;\n *\n * // ✓ Right — bridge the actual iframe\n * iframeRef.current = playerRef.current.iframeElement;\n * ```\n */\n get iframeElement(): HTMLIFrameElement {\n return this.iframe;\n }\n\n play() {\n this._hidePoster();\n this._playParentMedia();\n this._sendControl(\"play\");\n this._paused = false;\n this.controlsApi?.updatePlaying(true);\n this.dispatchEvent(new Event(\"play\"));\n }\n\n pause() {\n this._pauseParentMedia();\n this._sendControl(\"pause\");\n this._paused = true;\n this.controlsApi?.updatePlaying(false);\n this.dispatchEvent(new Event(\"pause\"));\n }\n\n seek(timeInSeconds: number) {\n const frame = Math.round(timeInSeconds * DEFAULT_FPS);\n this._sendControl(\"seek\", { frame });\n this._currentTime = timeInSeconds;\n\n // Sync parent media positions (accounting for each element's start offset)\n for (const m of this._parentMedia) {\n const relTime = timeInSeconds - m.start;\n if (relTime >= 0 && relTime < m.duration) {\n m.el.currentTime = relTime;\n }\n }\n\n this._paused = true;\n this.controlsApi?.updatePlaying(false);\n this.controlsApi?.updateTime(this._currentTime, this._duration);\n }\n\n get currentTime() {\n return this._currentTime;\n }\n set currentTime(t: number) {\n this.seek(t);\n }\n\n get duration() {\n return this._duration;\n }\n get paused() {\n return this._paused;\n }\n get ready() {\n return this._ready;\n }\n\n get playbackRate() {\n return parseFloat(this.getAttribute(\"playback-rate\") || \"1\");\n }\n set playbackRate(r: number) {\n this.setAttribute(\"playback-rate\", String(r));\n }\n\n get muted() {\n return this.hasAttribute(\"muted\");\n }\n set muted(m: boolean) {\n if (m) this.setAttribute(\"muted\", \"\");\n else this.removeAttribute(\"muted\");\n }\n\n get loop() {\n return this.hasAttribute(\"loop\");\n }\n set loop(l: boolean) {\n if (l) this.setAttribute(\"loop\", \"\");\n else this.removeAttribute(\"loop\");\n }\n\n // ── Private ──\n\n private _sendControl(action: string, extra: Record<string, unknown> = {}) {\n try {\n this.iframe.contentWindow?.postMessage(\n { source: \"hf-parent\", type: \"control\", action, ...extra },\n \"*\",\n );\n } catch {\n /* cross-origin */\n }\n }\n\n private _isControlsClick(event: Event) {\n return event\n .composedPath()\n .some((target) => target instanceof HTMLElement && target.classList.contains(\"hfp-controls\"));\n }\n\n private _onMessage(e: MessageEvent) {\n if (e.source !== this.iframe.contentWindow) return;\n const data = e.data;\n if (!data || data.source !== \"hf-preview\") return;\n\n if (data.type === \"state\") {\n this._currentTime = (data.frame ?? 0) / DEFAULT_FPS;\n const wasPlaying = !this._paused;\n this._paused = !data.isPlaying;\n\n // Sync parent media on runtime play/pause transitions (e.g. browser\n // throttling, visibility change, or scrubber interaction in the iframe).\n if (wasPlaying && this._paused) {\n this._pauseParentMedia();\n } else if (!wasPlaying && !this._paused) {\n this._playParentMedia();\n }\n\n // Throttle UI updates and event dispatch to ~10fps to avoid excessive re-renders\n const now = performance.now();\n if (now - this._lastUpdateMs > 100 || this._paused !== wasPlaying) {\n this._lastUpdateMs = now;\n this.controlsApi?.updateTime(this._currentTime, this._duration);\n this.controlsApi?.updatePlaying(!this._paused);\n this.dispatchEvent(\n new CustomEvent(\"timeupdate\", { detail: { currentTime: this._currentTime } }),\n );\n }\n\n if (this._currentTime >= this._duration && !this._paused) {\n this._pauseParentMedia();\n if (this.loop) {\n this.seek(0);\n this.play();\n } else {\n this._paused = true;\n this.controlsApi?.updatePlaying(false);\n this.dispatchEvent(new Event(\"ended\"));\n }\n }\n }\n\n if (data.type === \"timeline\" && data.durationInFrames > 0) {\n // Ignore Infinity duration from runtime (caused by loop-inflated timelines without data-duration)\n // The player already has duration from the initial probe, so keep that.\n if (Number.isFinite(data.durationInFrames)) {\n this._duration = data.durationInFrames / DEFAULT_FPS;\n this.controlsApi?.updateTime(this._currentTime, this._duration);\n }\n }\n\n if (data.type === \"stage-size\" && data.width > 0 && data.height > 0) {\n this._compositionWidth = data.width;\n this._compositionHeight = data.height;\n this._updateScale();\n }\n }\n\n private _runtimeInjected = false;\n\n private _onIframeLoad() {\n let attempts = 0;\n this._runtimeInjected = false;\n if (this._probeInterval) clearInterval(this._probeInterval);\n\n this._probeInterval = setInterval(() => {\n attempts++;\n try {\n const win = this.iframe.contentWindow as Window & {\n __player?: { getDuration: () => number };\n __timelines?: Record<string, { duration: () => number }>;\n __hf?: unknown;\n };\n if (!win) return;\n\n // Check if the runtime bridge is active (__hf or __player from the runtime)\n const hasRuntime = !!(win.__hf || win.__player);\n const hasTimelines = !!(win.__timelines && Object.keys(win.__timelines).length > 0);\n\n // Auto-inject runtime if GSAP timelines exist but no runtime bridge\n if (!hasRuntime && hasTimelines && !this._runtimeInjected && attempts >= 5) {\n this._injectRuntime();\n return; // Wait for runtime to load and initialize\n }\n\n // Runtime was injected but hasn't loaded yet — keep waiting\n if (this._runtimeInjected && !hasRuntime) {\n return;\n }\n\n const getAdapter = () => {\n if (win.__player && typeof win.__player.getDuration === \"function\") return win.__player;\n if (win.__timelines) {\n const keys = Object.keys(win.__timelines);\n if (keys.length > 0) {\n // Resolve the root composition id from the DOM — the outermost\n // `[data-composition-id]` element is the master. Bundled previews\n // register the root composition alongside sub-compositions, and\n // without this lookup Object.keys() order would make a\n // sub-composition's duration hijack the overall video length.\n const rootId = this.iframe.contentDocument\n ?.querySelector(\"[data-composition-id]\")\n ?.getAttribute(\"data-composition-id\");\n const key = rootId && rootId in win.__timelines ? rootId : keys[keys.length - 1];\n const tl = win.__timelines[key];\n return { getDuration: () => tl.duration() };\n }\n }\n return null;\n };\n\n const adapter = getAdapter();\n if (adapter && adapter.getDuration() > 0) {\n clearInterval(this._probeInterval!);\n this._duration = adapter.getDuration();\n this._ready = true;\n this.controlsApi?.updateTime(0, this._duration);\n this.dispatchEvent(new CustomEvent(\"ready\", { detail: { duration: this._duration } }));\n\n // Auto-detect dimensions from composition\n const doc = this.iframe.contentDocument;\n const root = doc?.querySelector(\"[data-composition-id]\");\n if (root) {\n const w = parseInt(root.getAttribute(\"data-width\") || \"0\", 10);\n const h = parseInt(root.getAttribute(\"data-height\") || \"0\", 10);\n if (w > 0 && h > 0) {\n this._compositionWidth = w;\n this._compositionHeight = h;\n this._updateScale();\n }\n }\n\n this._setupParentMedia();\n\n if (this.hasAttribute(\"autoplay\")) {\n this.play();\n }\n return;\n }\n } catch {\n /* cross-origin */\n }\n\n if (attempts >= 40) {\n clearInterval(this._probeInterval!);\n this.dispatchEvent(\n new CustomEvent(\"error\", {\n detail: { message: \"Composition timeline not found after 8s\" },\n }),\n );\n }\n }, 200);\n }\n\n /** Inject the HyperFrames runtime into the iframe if not already present. */\n private _injectRuntime() {\n this._runtimeInjected = true;\n try {\n const doc = this.iframe.contentDocument;\n if (!doc) return;\n const script = doc.createElement(\"script\");\n script.src = RUNTIME_CDN_URL;\n script.onload = () => {\n // Runtime loaded — the probe interval will pick up __hf on next tick\n };\n script.onerror = () => {\n // CDN failed — the probe will continue and eventually timeout\n };\n (doc.head || doc.documentElement).appendChild(script);\n } catch {\n /* cross-origin — can't inject */\n }\n }\n\n private _updateScale() {\n const rect = this.getBoundingClientRect();\n if (rect.width === 0 || rect.height === 0) return;\n const scale = Math.min(\n rect.width / this._compositionWidth,\n rect.height / this._compositionHeight,\n );\n this.iframe.style.width = `${this._compositionWidth}px`;\n this.iframe.style.height = `${this._compositionHeight}px`;\n this.iframe.style.transform = `translate(-50%, -50%) scale(${scale})`;\n }\n\n private _setupControls() {\n if (this.controlsApi) return;\n const callbacks: ControlsCallbacks = {\n onPlay: () => this.play(),\n onPause: () => this.pause(),\n onSeek: (fraction) => this.seek(fraction * this._duration),\n onSpeedChange: (speed) => {\n this.playbackRate = speed;\n },\n };\n const presetsAttr = this.getAttribute(\"speed-presets\");\n const speedPresets = presetsAttr\n ? presetsAttr\n .split(\",\")\n .map(Number)\n .filter((n) => !isNaN(n) && n > 0)\n : undefined;\n this.controlsApi = createControls(this.shadow, callbacks, { speedPresets });\n }\n\n private _setupPoster() {\n const url = this.getAttribute(\"poster\");\n if (!url) {\n this.posterEl?.remove();\n this.posterEl = null;\n return;\n }\n if (!this.posterEl) {\n this.posterEl = document.createElement(\"img\");\n this.posterEl.className = \"hfp-poster\";\n this.shadow.appendChild(this.posterEl);\n }\n this.posterEl.src = url;\n }\n\n private _playParentMedia() {\n for (const m of this._parentMedia) {\n if (m.el.src) m.el.play().catch(() => {});\n }\n }\n\n private _pauseParentMedia() {\n for (const m of this._parentMedia) m.el.pause();\n }\n\n /** Create a parent-frame media element, configure it, and start preloading. */\n private _createParentMedia(src: string, tag: \"audio\" | \"video\", start: number, duration: number) {\n // Deduplicate — browsers normalize URLs so we compare on the element after assignment\n if (this._parentMedia.some((m) => m.el.src === src)) return;\n\n const el = tag === \"video\" ? document.createElement(\"video\") : new Audio();\n el.preload = \"auto\";\n el.src = src;\n el.load();\n el.muted = this.muted;\n if (this.playbackRate !== 1) el.playbackRate = this.playbackRate;\n\n this._parentMedia.push({ el, start, duration });\n }\n\n /**\n * Set up a single parent-frame audio from an explicit URL (via `audio-src`).\n * Convenience for the common single-narration case — starts preloading\n * immediately without waiting for the iframe to load.\n */\n private _setupParentAudioFromUrl(audioSrc: string) {\n this._createParentMedia(audioSrc, \"audio\", 0, Infinity);\n }\n\n /**\n * Extract ALL timed media (audio/video with data-start) from the iframe's\n * DOM and create parent-frame copies. Disables the iframe originals so the\n * runtime doesn't try to play them (which would fail on mobile and cause\n * double playback on desktop).\n *\n * If `audio-src` was already set, this just disables the iframe media.\n */\n private _setupParentMedia() {\n try {\n const doc = this.iframe.contentDocument;\n if (!doc) return;\n\n // Find all timed media — matches the runtime's media.ts selector\n const mediaEls = doc.querySelectorAll<HTMLMediaElement>(\n \"audio[data-start], video[data-start]\",\n );\n\n for (const iframeEl of mediaEls) {\n const src = iframeEl.getAttribute(\"src\") || iframeEl.querySelector(\"source\")?.src;\n if (!src) continue;\n\n const start = parseFloat(iframeEl.getAttribute(\"data-start\") || \"0\");\n const duration = parseFloat(iframeEl.getAttribute(\"data-duration\") || \"Infinity\");\n const tag = iframeEl.tagName === \"VIDEO\" ? (\"video\" as const) : (\"audio\" as const);\n\n this._createParentMedia(src, tag, start, duration);\n\n // Disable the iframe element so the runtime ignores it\n iframeEl.removeAttribute(\"src\");\n iframeEl.removeAttribute(\"data-start\");\n iframeEl.removeAttribute(\"data-duration\");\n iframeEl.querySelectorAll(\"source\").forEach((s) => s.remove());\n }\n } catch {\n // Cross-origin iframe — can't access DOM, fall back to iframe media\n }\n }\n\n private _hidePoster() {\n this.posterEl?.remove();\n this.posterEl = null;\n }\n}\n\nif (!customElements.get(\"hyperframes-player\")) {\n customElements.define(\"hyperframes-player\", HyperframesPlayer);\n}\n\nexport { HyperframesPlayer };\nexport { formatTime, formatSpeed, SPEED_PRESETS } from \"./controls.js\";\nexport type { ControlsCallbacks, ControlsOptions } from \"./controls.js\";\n","export const PLAYER_STYLES = /* css */ `\n :host {\n display: block;\n position: relative;\n overflow: hidden;\n background: #000;\n contain: layout style;\n }\n\n .hfp-container {\n position: absolute;\n inset: 0;\n overflow: hidden;\n pointer-events: none;\n }\n\n\n .hfp-iframe {\n position: absolute;\n top: 50%;\n left: 50%;\n border: none;\n pointer-events: none;\n }\n\n .hfp-poster {\n position: absolute;\n inset: 0;\n object-fit: contain;\n z-index: 1;\n pointer-events: none;\n }\n\n /* ── Theming via CSS custom properties ──\n *\n * Override from outside the shadow DOM:\n * hyperframes-player {\n * --hfp-controls-bg: linear-gradient(transparent, rgba(0,0,0,0.9));\n * --hfp-accent: #ff6b6b;\n * --hfp-font: \"Inter\", sans-serif;\n * }\n */\n\n .hfp-controls {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n display: flex;\n align-items: center;\n gap: var(--hfp-controls-gap, 12px);\n padding: var(--hfp-controls-padding, 8px 16px);\n background: var(--hfp-controls-bg, linear-gradient(transparent, rgba(0, 0, 0, 0.7)));\n color: var(--hfp-color, #fff);\n font-family: var(--hfp-font, system-ui, -apple-system, sans-serif);\n font-size: var(--hfp-font-size, 13px);\n z-index: 10;\n pointer-events: auto;\n opacity: 1;\n transition: opacity 0.3s ease;\n user-select: none;\n }\n\n .hfp-controls.hfp-hidden {\n opacity: 0;\n pointer-events: none;\n }\n\n .hfp-play-btn {\n background: none;\n border: none;\n color: var(--hfp-color, #fff);\n cursor: pointer;\n padding: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n flex-shrink: 0;\n z-index: 10;\n }\n\n .hfp-play-btn:hover {\n opacity: 0.8;\n }\n\n .hfp-play-btn svg,\n .hfp-play-btn svg * {\n pointer-events: none;\n }\n\n .hfp-scrubber {\n flex: 1;\n height: var(--hfp-scrubber-height, 4px);\n background: var(--hfp-scrubber-bg, rgba(255, 255, 255, 0.3));\n border-radius: var(--hfp-scrubber-radius, 2px);\n cursor: pointer;\n position: relative;\n }\n\n .hfp-scrubber:hover {\n height: var(--hfp-scrubber-height-hover, 6px);\n }\n\n .hfp-progress {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n background: var(--hfp-accent, #fff);\n border-radius: var(--hfp-scrubber-radius, 2px);\n pointer-events: none;\n }\n\n .hfp-time {\n flex-shrink: 0;\n font-variant-numeric: tabular-nums;\n opacity: 0.9;\n }\n\n .hfp-speed-wrap {\n position: relative;\n flex-shrink: 0;\n }\n\n .hfp-speed-btn {\n background: var(--hfp-speed-btn-bg, rgba(255, 255, 255, 0.15));\n border: none;\n border-radius: var(--hfp-speed-btn-radius, 4px);\n color: var(--hfp-color, #fff);\n cursor: pointer;\n font-family: var(--hfp-font, system-ui, -apple-system, sans-serif);\n font-size: 12px;\n font-variant-numeric: tabular-nums;\n font-weight: 600;\n padding: 4px 8px;\n min-width: 40px;\n text-align: center;\n transition: background 0.15s ease;\n }\n\n .hfp-speed-btn:hover {\n background: var(--hfp-speed-btn-bg-hover, rgba(255, 255, 255, 0.3));\n }\n\n .hfp-speed-menu {\n position: absolute;\n bottom: calc(100% + 8px);\n right: 0;\n background: var(--hfp-menu-bg, rgba(20, 20, 20, 0.95));\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n border: 1px solid var(--hfp-menu-border, rgba(255, 255, 255, 0.1));\n border-radius: var(--hfp-menu-radius, 8px);\n padding: 4px;\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 80px;\n opacity: 0;\n visibility: hidden;\n transform: translateY(4px);\n transition: opacity 0.15s ease, transform 0.15s ease, visibility 0.15s;\n box-shadow: var(--hfp-menu-shadow, 0 8px 24px rgba(0, 0, 0, 0.4));\n }\n\n .hfp-speed-menu.hfp-open {\n opacity: 1;\n visibility: visible;\n transform: translateY(0);\n }\n\n .hfp-speed-option {\n background: none;\n border: none;\n border-radius: 4px;\n color: var(--hfp-menu-color, rgba(255, 255, 255, 0.7));\n cursor: pointer;\n font-family: var(--hfp-font, system-ui, -apple-system, sans-serif);\n font-size: 13px;\n font-variant-numeric: tabular-nums;\n padding: 6px 12px;\n text-align: left;\n transition: background 0.1s ease, color 0.1s ease;\n white-space: nowrap;\n }\n\n .hfp-speed-option:hover {\n background: var(--hfp-menu-hover-bg, rgba(255, 255, 255, 0.1));\n color: var(--hfp-color, #fff);\n }\n\n .hfp-speed-option.hfp-active {\n color: var(--hfp-accent, #fff);\n font-weight: 600;\n }\n`;\n\nexport const PLAY_ICON = `<svg width=\"24\" height=\"24\" viewBox=\"0 0 18 18\" fill=\"currentColor\"><polygon points=\"4,2 16,9 4,16\"/></svg>`;\nexport const PAUSE_ICON = `<svg width=\"24\" height=\"24\" viewBox=\"0 0 18 18\" fill=\"currentColor\"><rect x=\"3\" y=\"2\" width=\"4\" height=\"14\"/><rect x=\"11\" y=\"2\" width=\"4\" height=\"14\"/></svg>`;\n","import { PLAY_ICON, PAUSE_ICON } from \"./styles.js\";\n\nexport interface ControlsCallbacks {\n onPlay: () => void;\n onPause: () => void;\n onSeek: (fraction: number) => void;\n onSpeedChange: (speed: number) => void;\n}\n\n/** Default logarithmic speed presets — each step roughly doubles/halves. */\nexport const SPEED_PRESETS = [0.25, 0.5, 1, 1.5, 2, 4] as const;\n\nexport interface ControlsOptions {\n /** Speed presets shown in the menu. Defaults to SPEED_PRESETS. */\n speedPresets?: readonly number[];\n}\n\nexport function formatSpeed(speed: number): string {\n return Number.isInteger(speed) ? `${speed}x` : `${speed}x`;\n}\n\nexport function formatTime(seconds: number): string {\n // Handle non-finite values gracefully\n if (!Number.isFinite(seconds) || seconds < 0) {\n return \"0:00\";\n }\n const s = Math.floor(seconds);\n const m = Math.floor(s / 60);\n const sec = s % 60;\n return `${m}:${sec.toString().padStart(2, \"0\")}`;\n}\n\nexport function createControls(\n parent: ShadowRoot | HTMLElement,\n callbacks: ControlsCallbacks,\n options: ControlsOptions = {},\n): {\n updateTime: (current: number, duration: number) => void;\n updatePlaying: (playing: boolean) => void;\n updateSpeed: (speed: number) => void;\n show: () => void;\n hide: () => void;\n destroy: () => void;\n} {\n const presets = options.speedPresets ?? SPEED_PRESETS;\n\n const controls = document.createElement(\"div\");\n controls.className = \"hfp-controls\";\n // Keep overlay interactions from falling through to the host-level click toggle.\n controls.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n });\n\n const playBtn = document.createElement(\"button\");\n playBtn.className = \"hfp-play-btn\";\n playBtn.type = \"button\";\n playBtn.innerHTML = PLAY_ICON;\n playBtn.setAttribute(\"aria-label\", \"Play\");\n\n const scrubber = document.createElement(\"div\");\n scrubber.className = \"hfp-scrubber\";\n const progress = document.createElement(\"div\");\n progress.className = \"hfp-progress\";\n progress.style.width = \"0%\";\n scrubber.appendChild(progress);\n\n const time = document.createElement(\"span\");\n time.className = \"hfp-time\";\n time.textContent = \"0:00 / 0:00\";\n\n const speedWrap = document.createElement(\"div\");\n speedWrap.className = \"hfp-speed-wrap\";\n\n const speedBtn = document.createElement(\"button\");\n speedBtn.className = \"hfp-speed-btn\";\n speedBtn.type = \"button\";\n speedBtn.textContent = \"1x\";\n speedBtn.setAttribute(\"aria-label\", \"Playback speed\");\n\n const speedMenu = document.createElement(\"div\");\n speedMenu.className = \"hfp-speed-menu\";\n speedMenu.setAttribute(\"role\", \"menu\");\n for (const preset of presets) {\n const item = document.createElement(\"button\");\n item.className = \"hfp-speed-option\";\n item.type = \"button\";\n item.setAttribute(\"role\", \"menuitem\");\n item.dataset.speed = String(preset);\n item.textContent = formatSpeed(preset);\n if (preset === 1) item.classList.add(\"hfp-active\");\n speedMenu.appendChild(item);\n }\n\n speedWrap.appendChild(speedMenu);\n speedWrap.appendChild(speedBtn);\n\n controls.appendChild(playBtn);\n controls.appendChild(scrubber);\n controls.appendChild(time);\n controls.appendChild(speedWrap);\n parent.appendChild(controls);\n\n let isPlaying = false;\n let hideTimeout: ReturnType<typeof setTimeout> | null = null;\n let speedIndex = presets.indexOf(1); // start at 1x\n if (speedIndex === -1) speedIndex = 0;\n\n playBtn.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n if (isPlaying) callbacks.onPause();\n else callbacks.onPlay();\n });\n\n const setActiveOption = (speed: number) => {\n for (const opt of speedMenu.querySelectorAll(\".hfp-speed-option\")) {\n opt.classList.toggle(\"hfp-active\", (opt as HTMLElement).dataset.speed === String(speed));\n }\n };\n\n speedBtn.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n const isOpen = speedMenu.classList.toggle(\"hfp-open\");\n speedBtn.setAttribute(\"aria-expanded\", String(isOpen));\n });\n\n speedMenu.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n const target = (e.target as HTMLElement).closest(\".hfp-speed-option\") as HTMLElement | null;\n if (!target) return;\n const newSpeed = parseFloat(target.dataset.speed!);\n speedIndex = presets.indexOf(newSpeed);\n speedBtn.textContent = formatSpeed(newSpeed);\n setActiveOption(newSpeed);\n speedMenu.classList.remove(\"hfp-open\");\n speedBtn.setAttribute(\"aria-expanded\", \"false\");\n callbacks.onSpeedChange(newSpeed);\n });\n\n // Close menu when clicking outside\n const onDocClick = () => {\n speedMenu.classList.remove(\"hfp-open\");\n speedBtn.setAttribute(\"aria-expanded\", \"false\");\n };\n document.addEventListener(\"click\", onDocClick);\n\n const handleScrubAt = (clientX: number) => {\n const rect = scrubber.getBoundingClientRect();\n const fraction = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));\n callbacks.onSeek(fraction);\n };\n\n let scrubbing = false;\n\n scrubber.addEventListener(\"mousedown\", (e) => {\n e.stopPropagation();\n scrubbing = true;\n handleScrubAt(e.clientX);\n });\n const onMouseMove = (e: MouseEvent) => {\n if (scrubbing) handleScrubAt(e.clientX);\n };\n const onMouseUp = () => {\n scrubbing = false;\n };\n document.addEventListener(\"mousemove\", onMouseMove);\n document.addEventListener(\"mouseup\", onMouseUp);\n\n scrubber.addEventListener(\n \"touchstart\",\n (e) => {\n scrubbing = true;\n const touch = e.touches[0];\n if (touch) handleScrubAt(touch.clientX);\n },\n { passive: true },\n );\n const onTouchMove = (e: TouchEvent) => {\n if (scrubbing) {\n const touch = e.touches[0];\n if (touch) handleScrubAt(touch.clientX);\n }\n };\n const onTouchEnd = () => {\n scrubbing = false;\n };\n document.addEventListener(\"touchmove\", onTouchMove, { passive: true });\n document.addEventListener(\"touchend\", onTouchEnd);\n\n const startHideTimer = () => {\n if (hideTimeout) clearTimeout(hideTimeout);\n hideTimeout = setTimeout(() => {\n if (isPlaying) controls.classList.add(\"hfp-hidden\");\n }, 3000);\n };\n\n const host = parent instanceof ShadowRoot ? (parent.host as HTMLElement) : parent;\n host.addEventListener(\"mousemove\", () => {\n controls.classList.remove(\"hfp-hidden\");\n startHideTimer();\n });\n host.addEventListener(\"mouseleave\", () => {\n if (isPlaying) controls.classList.add(\"hfp-hidden\");\n });\n\n return {\n updateTime(current: number, duration: number) {\n const pct = duration > 0 ? (current / duration) * 100 : 0;\n progress.style.width = `${pct}%`;\n time.textContent = `${formatTime(current)} / ${formatTime(duration)}`;\n },\n updatePlaying(playing: boolean) {\n isPlaying = playing;\n playBtn.innerHTML = playing ? PAUSE_ICON : PLAY_ICON;\n playBtn.setAttribute(\"aria-label\", playing ? \"Pause\" : \"Play\");\n if (playing) startHideTimer();\n else controls.classList.remove(\"hfp-hidden\");\n },\n updateSpeed(speed: number) {\n const idx = presets.indexOf(speed);\n if (idx !== -1) speedIndex = idx;\n speedBtn.textContent = formatSpeed(speed);\n setActiveOption(speed);\n },\n show() {\n controls.style.display = \"\";\n },\n hide() {\n controls.style.display = \"none\";\n },\n destroy() {\n document.removeEventListener(\"mousemove\", onMouseMove);\n document.removeEventListener(\"mouseup\", onMouseUp);\n document.removeEventListener(\"touchmove\", onTouchMove);\n document.removeEventListener(\"touchend\", onTouchEnd);\n document.removeEventListener(\"click\", onDocClick);\n if (hideTimeout) clearTimeout(hideTimeout);\n },\n };\n}\n"],"mappings":"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","names":["hyperframes_player_exports","__export","HyperframesPlayer","SPEED_PRESETS","formatSpeed","formatTime","PLAYER_STYLES","PLAY_ICON","PAUSE_ICON","SPEED_PRESETS","formatSpeed","speed","formatTime","seconds","s","m","sec","createControls","parent","callbacks","options","presets","controls","e","playBtn","PLAY_ICON","scrubber","progress","time","speedWrap","speedBtn","speedMenu","preset","item","isPlaying","hideTimeout","speedIndex","setActiveOption","opt","isOpen","target","newSpeed","onDocClick","handleScrubAt","clientX","rect","fraction","scrubbing","onMouseMove","onMouseUp","touch","onTouchMove","onTouchEnd","startHideTimer","host","current","duration","pct","playing","PAUSE_ICON","idx","DEFAULT_FPS","RUNTIME_CDN_URL","HyperframesPlayer","style","PLAYER_STYLES","event","m","name","_old","val","rate","timeInSeconds","frame","relTime","t","r","l","action","extra","target","data","wasPlaying","now","attempts","win","hasRuntime","hasTimelines","adapter","keys","rootId","key","tl","root","w","h","doc","script","rect","scale","callbacks","fraction","speed","presetsAttr","speedPresets","n","createControls","url","src","tag","start","duration","el","audioSrc","mediaEls","iframeEl","s"]}
|
|
1
|
+
{"version":3,"sources":["../src/hyperframes-player.ts","../src/styles.ts","../src/controls.ts"],"sourcesContent":["import { createControls, SPEED_PRESETS, type ControlsCallbacks } from \"./controls.js\";\nimport { PLAYER_STYLES } from \"./styles.js\";\n\nconst DEFAULT_FPS = 30;\nconst RUNTIME_CDN_URL =\n \"https://cdn.jsdelivr.net/npm/@hyperframes/core/dist/hyperframe.runtime.iife.js\";\n\nclass HyperframesPlayer extends HTMLElement {\n static get observedAttributes() {\n return [\"src\", \"width\", \"height\", \"controls\", \"muted\", \"poster\", \"playback-rate\", \"audio-src\"];\n }\n\n private shadow: ShadowRoot;\n private container: HTMLDivElement;\n private iframe: HTMLIFrameElement;\n private posterEl: HTMLImageElement | null = null;\n private controlsApi: ReturnType<typeof createControls> | null = null;\n private resizeObserver: ResizeObserver;\n\n private _ready = false;\n private _duration = 0;\n private _currentTime = 0;\n private _paused = true;\n private _compositionWidth = 1920;\n private _compositionHeight = 1080;\n private _probeInterval: ReturnType<typeof setInterval> | null = null;\n private _lastUpdateMs = 0;\n\n /**\n * Parent-frame media elements for mobile playback.\n *\n * Mobile browsers block media.play() inside iframes when the user gesture\n * happened in the parent frame — postMessage doesn't transfer user activation\n * (per the User Activation v2 spec). We extract ALL media sources from the\n * iframe's timed elements (audio/video with data-start), play them in the\n * parent frame (where the gesture lives), and disable the iframe copies.\n */\n private _parentMedia: Array<{\n el: HTMLMediaElement;\n start: number;\n duration: number;\n }> = [];\n\n constructor() {\n super();\n this.shadow = this.attachShadow({ mode: \"open\" });\n\n const style = document.createElement(\"style\");\n style.textContent = PLAYER_STYLES;\n this.shadow.appendChild(style);\n\n this.container = document.createElement(\"div\");\n this.container.className = \"hfp-container\";\n\n this.iframe = document.createElement(\"iframe\");\n this.iframe.className = \"hfp-iframe\";\n this.iframe.sandbox.add(\"allow-scripts\", \"allow-same-origin\");\n this.iframe.allow = \"autoplay; fullscreen\";\n this.iframe.referrerPolicy = \"no-referrer\";\n this.iframe.title = \"HyperFrames Composition\";\n\n this.container.appendChild(this.iframe);\n this.shadow.appendChild(this.container);\n\n // Clicking the bare player surface toggles play/pause.\n // Ignore shadow-DOM control interactions so overlay clicks don't double-handle.\n this.addEventListener(\"click\", (event) => {\n if (this._isControlsClick(event)) return;\n if (this._paused) this.play();\n else this.pause();\n });\n\n this.resizeObserver = new ResizeObserver(() => this._updateScale());\n\n this._onMessage = this._onMessage.bind(this);\n this._onIframeLoad = this._onIframeLoad.bind(this);\n }\n\n connectedCallback() {\n this.resizeObserver.observe(this);\n window.addEventListener(\"message\", this._onMessage);\n this.iframe.addEventListener(\"load\", this._onIframeLoad);\n\n if (this.hasAttribute(\"controls\")) this._setupControls();\n if (this.hasAttribute(\"poster\")) this._setupPoster();\n if (this.hasAttribute(\"audio-src\"))\n this._setupParentAudioFromUrl(this.getAttribute(\"audio-src\")!);\n if (this.hasAttribute(\"src\")) this.iframe.src = this.getAttribute(\"src\")!;\n }\n\n disconnectedCallback() {\n this.resizeObserver.disconnect();\n window.removeEventListener(\"message\", this._onMessage);\n this.iframe.removeEventListener(\"load\", this._onIframeLoad);\n if (this._probeInterval) clearInterval(this._probeInterval);\n this.controlsApi?.destroy();\n for (const m of this._parentMedia) {\n m.el.pause();\n m.el.src = \"\";\n }\n this._parentMedia = [];\n }\n\n attributeChangedCallback(name: string, _old: string | null, val: string | null) {\n switch (name) {\n case \"src\":\n if (val) {\n this._ready = false;\n this.iframe.src = val;\n }\n break;\n case \"width\":\n this._compositionWidth = parseInt(val || \"1920\", 10);\n this._updateScale();\n break;\n case \"height\":\n this._compositionHeight = parseInt(val || \"1080\", 10);\n this._updateScale();\n break;\n case \"controls\":\n if (val !== null) this._setupControls();\n else {\n this.controlsApi?.destroy();\n this.controlsApi = null;\n }\n break;\n case \"poster\":\n this._setupPoster();\n break;\n case \"playback-rate\": {\n const rate = parseFloat(val || \"1\");\n for (const m of this._parentMedia) m.el.playbackRate = rate;\n this._sendControl(\"set-playback-rate\", { playbackRate: rate });\n this.controlsApi?.updateSpeed(rate);\n this.dispatchEvent(new Event(\"ratechange\"));\n break;\n }\n case \"muted\":\n for (const m of this._parentMedia) m.el.muted = val !== null;\n this._sendControl(\"set-muted\", { muted: val !== null });\n break;\n case \"audio-src\":\n if (val) this._setupParentAudioFromUrl(val);\n break;\n }\n }\n\n // ── Public API ──\n\n /**\n * Access the inner `<iframe>` element rendering the composition.\n *\n * Use this when integrating the player with editors, recorders, or\n * timeline tools (e.g. `@hyperframes/studio`) that need to inspect\n * the composition's DOM or read its `__player` / `__timelines`\n * runtime objects.\n *\n * **Common pitfall:** the iframe lives inside the player's Shadow DOM.\n * Passing the `<hyperframes-player>` element itself to code that expects\n * an `<iframe>` will silently break — `.contentWindow` returns `null`.\n * Always extract `iframeElement` first:\n *\n * ```ts\n * // ❌ Wrong — element ref doesn't expose contentWindow\n * iframeRef.current = playerRef.current;\n *\n * // ✓ Right — bridge the actual iframe\n * iframeRef.current = playerRef.current.iframeElement;\n * ```\n */\n get iframeElement(): HTMLIFrameElement {\n return this.iframe;\n }\n\n play() {\n this._hidePoster();\n this._playParentMedia();\n this._sendControl(\"play\");\n this._paused = false;\n this.controlsApi?.updatePlaying(true);\n this.dispatchEvent(new Event(\"play\"));\n }\n\n pause() {\n this._pauseParentMedia();\n this._sendControl(\"pause\");\n this._paused = true;\n this.controlsApi?.updatePlaying(false);\n this.dispatchEvent(new Event(\"pause\"));\n }\n\n seek(timeInSeconds: number) {\n const frame = Math.round(timeInSeconds * DEFAULT_FPS);\n this._sendControl(\"seek\", { frame });\n this._currentTime = timeInSeconds;\n\n // Sync parent media positions (accounting for each element's start offset)\n for (const m of this._parentMedia) {\n const relTime = timeInSeconds - m.start;\n if (relTime >= 0 && relTime < m.duration) {\n m.el.currentTime = relTime;\n }\n }\n\n this._paused = true;\n this.controlsApi?.updatePlaying(false);\n this.controlsApi?.updateTime(this._currentTime, this._duration);\n }\n\n get currentTime() {\n return this._currentTime;\n }\n set currentTime(t: number) {\n this.seek(t);\n }\n\n get duration() {\n return this._duration;\n }\n get paused() {\n return this._paused;\n }\n get ready() {\n return this._ready;\n }\n\n get playbackRate() {\n return parseFloat(this.getAttribute(\"playback-rate\") || \"1\");\n }\n set playbackRate(r: number) {\n this.setAttribute(\"playback-rate\", String(r));\n }\n\n get muted() {\n return this.hasAttribute(\"muted\");\n }\n set muted(m: boolean) {\n if (m) this.setAttribute(\"muted\", \"\");\n else this.removeAttribute(\"muted\");\n }\n\n get loop() {\n return this.hasAttribute(\"loop\");\n }\n set loop(l: boolean) {\n if (l) this.setAttribute(\"loop\", \"\");\n else this.removeAttribute(\"loop\");\n }\n\n // ── Private ──\n\n private _sendControl(action: string, extra: Record<string, unknown> = {}) {\n try {\n this.iframe.contentWindow?.postMessage(\n { source: \"hf-parent\", type: \"control\", action, ...extra },\n \"*\",\n );\n } catch {\n /* cross-origin */\n }\n }\n\n private _isControlsClick(event: Event) {\n return event\n .composedPath()\n .some((target) => target instanceof HTMLElement && target.classList.contains(\"hfp-controls\"));\n }\n\n private _onMessage(e: MessageEvent) {\n if (e.source !== this.iframe.contentWindow) return;\n const data = e.data;\n if (!data || data.source !== \"hf-preview\") return;\n\n if (data.type === \"state\") {\n this._currentTime = (data.frame ?? 0) / DEFAULT_FPS;\n const wasPlaying = !this._paused;\n this._paused = !data.isPlaying;\n\n // Sync parent media on runtime play/pause transitions (e.g. browser\n // throttling, visibility change, or scrubber interaction in the iframe).\n if (wasPlaying && this._paused) {\n this._pauseParentMedia();\n } else if (!wasPlaying && !this._paused) {\n this._playParentMedia();\n }\n\n // Throttle UI updates and event dispatch to ~10fps to avoid excessive re-renders\n const now = performance.now();\n if (now - this._lastUpdateMs > 100 || this._paused !== wasPlaying) {\n this._lastUpdateMs = now;\n this.controlsApi?.updateTime(this._currentTime, this._duration);\n this.controlsApi?.updatePlaying(!this._paused);\n this.dispatchEvent(\n new CustomEvent(\"timeupdate\", { detail: { currentTime: this._currentTime } }),\n );\n }\n\n if (this._currentTime >= this._duration && !this._paused) {\n this._pauseParentMedia();\n if (this.loop) {\n this.seek(0);\n this.play();\n } else {\n this._paused = true;\n this.controlsApi?.updatePlaying(false);\n this.dispatchEvent(new Event(\"ended\"));\n }\n }\n }\n\n if (data.type === \"timeline\" && data.durationInFrames > 0) {\n // Ignore Infinity duration from runtime (caused by loop-inflated timelines without data-duration)\n // The player already has duration from the initial probe, so keep that.\n if (Number.isFinite(data.durationInFrames)) {\n this._duration = data.durationInFrames / DEFAULT_FPS;\n this.controlsApi?.updateTime(this._currentTime, this._duration);\n }\n }\n\n if (data.type === \"stage-size\" && data.width > 0 && data.height > 0) {\n this._compositionWidth = data.width;\n this._compositionHeight = data.height;\n this._updateScale();\n }\n }\n\n private _runtimeInjected = false;\n\n private _onIframeLoad() {\n let attempts = 0;\n this._runtimeInjected = false;\n if (this._probeInterval) clearInterval(this._probeInterval);\n\n this._probeInterval = setInterval(() => {\n attempts++;\n try {\n const win = this.iframe.contentWindow as Window & {\n __player?: { getDuration: () => number };\n __timelines?: Record<string, { duration: () => number }>;\n __hf?: unknown;\n };\n if (!win) return;\n\n // Check if the runtime bridge is active (__hf or __player from the runtime)\n const hasRuntime = !!(win.__hf || win.__player);\n const hasTimelines = !!(win.__timelines && Object.keys(win.__timelines).length > 0);\n\n // Auto-inject runtime if GSAP timelines exist but no runtime bridge\n if (!hasRuntime && hasTimelines && !this._runtimeInjected && attempts >= 5) {\n this._injectRuntime();\n return; // Wait for runtime to load and initialize\n }\n\n // Runtime was injected but hasn't loaded yet — keep waiting\n if (this._runtimeInjected && !hasRuntime) {\n return;\n }\n\n const getAdapter = () => {\n if (win.__player && typeof win.__player.getDuration === \"function\") return win.__player;\n if (win.__timelines) {\n const keys = Object.keys(win.__timelines);\n if (keys.length > 0) {\n // Resolve the root composition id from the DOM — the outermost\n // `[data-composition-id]` element is the master. Bundled previews\n // register the root composition alongside sub-compositions, and\n // without this lookup Object.keys() order would make a\n // sub-composition's duration hijack the overall video length.\n const rootId = this.iframe.contentDocument\n ?.querySelector(\"[data-composition-id]\")\n ?.getAttribute(\"data-composition-id\");\n const key = rootId && rootId in win.__timelines ? rootId : keys[keys.length - 1];\n const tl = win.__timelines[key];\n return { getDuration: () => tl.duration() };\n }\n }\n return null;\n };\n\n const adapter = getAdapter();\n if (adapter && adapter.getDuration() > 0) {\n clearInterval(this._probeInterval!);\n this._duration = adapter.getDuration();\n this._ready = true;\n this.controlsApi?.updateTime(0, this._duration);\n this.dispatchEvent(new CustomEvent(\"ready\", { detail: { duration: this._duration } }));\n\n // Auto-detect dimensions from composition\n const doc = this.iframe.contentDocument;\n const root = doc?.querySelector(\"[data-composition-id]\");\n if (root) {\n const w = parseInt(root.getAttribute(\"data-width\") || \"0\", 10);\n const h = parseInt(root.getAttribute(\"data-height\") || \"0\", 10);\n if (w > 0 && h > 0) {\n this._compositionWidth = w;\n this._compositionHeight = h;\n this._updateScale();\n }\n }\n\n this._setupParentMedia();\n\n if (this.hasAttribute(\"autoplay\")) {\n this.play();\n }\n return;\n }\n } catch {\n /* cross-origin */\n }\n\n if (attempts >= 40) {\n clearInterval(this._probeInterval!);\n this.dispatchEvent(\n new CustomEvent(\"error\", {\n detail: { message: \"Composition timeline not found after 8s\" },\n }),\n );\n }\n }, 200);\n }\n\n /** Inject the HyperFrames runtime into the iframe if not already present. */\n private _injectRuntime() {\n this._runtimeInjected = true;\n try {\n const doc = this.iframe.contentDocument;\n if (!doc) return;\n const script = doc.createElement(\"script\");\n script.src = RUNTIME_CDN_URL;\n script.onload = () => {\n // Runtime loaded — the probe interval will pick up __hf on next tick\n };\n script.onerror = () => {\n // CDN failed — the probe will continue and eventually timeout\n };\n (doc.head || doc.documentElement).appendChild(script);\n } catch {\n /* cross-origin — can't inject */\n }\n }\n\n private _updateScale() {\n const rect = this.getBoundingClientRect();\n if (rect.width === 0 || rect.height === 0) return;\n const scale = Math.min(\n rect.width / this._compositionWidth,\n rect.height / this._compositionHeight,\n );\n this.iframe.style.width = `${this._compositionWidth}px`;\n this.iframe.style.height = `${this._compositionHeight}px`;\n this.iframe.style.transform = `translate(-50%, -50%) scale(${scale})`;\n }\n\n private _setupControls() {\n if (this.controlsApi) return;\n const callbacks: ControlsCallbacks = {\n onPlay: () => this.play(),\n onPause: () => this.pause(),\n onSeek: (fraction) => this.seek(fraction * this._duration),\n onSpeedChange: (speed) => {\n this.playbackRate = speed;\n },\n };\n const presetsAttr = this.getAttribute(\"speed-presets\");\n const speedPresets = presetsAttr\n ? presetsAttr\n .split(\",\")\n .map(Number)\n .filter((n) => !isNaN(n) && n > 0)\n : undefined;\n this.controlsApi = createControls(this.shadow, callbacks, { speedPresets });\n }\n\n private _setupPoster() {\n const url = this.getAttribute(\"poster\");\n if (!url) {\n this.posterEl?.remove();\n this.posterEl = null;\n return;\n }\n if (!this.posterEl) {\n this.posterEl = document.createElement(\"img\");\n this.posterEl.className = \"hfp-poster\";\n this.shadow.appendChild(this.posterEl);\n }\n this.posterEl.src = url;\n }\n\n private _playParentMedia() {\n for (const m of this._parentMedia) {\n if (m.el.src) {\n m.el\n .play()\n .then(() => {\n // Parent play succeeded — mute the iframe copy to prevent double audio.\n // This runs asynchronously, so the runtime may briefly play both copies,\n // but the overlap is inaudible (same audio at the same position).\n this._muteIframeMedia();\n })\n .catch(() => {});\n }\n }\n }\n\n private _muteIframeMedia() {\n try {\n const doc = this.iframe.contentDocument;\n if (!doc) return;\n const mediaEls = doc.querySelectorAll<HTMLMediaElement>(\n \"audio[data-start], video[data-start]\",\n );\n for (const el of mediaEls) el.volume = 0;\n } catch {\n // cross-origin\n }\n }\n\n private _pauseParentMedia() {\n for (const m of this._parentMedia) m.el.pause();\n }\n\n /** Create a parent-frame media element, configure it, and start preloading. */\n private _createParentMedia(src: string, tag: \"audio\" | \"video\", start: number, duration: number) {\n // Deduplicate — browsers normalize URLs so we compare on the element after assignment\n if (this._parentMedia.some((m) => m.el.src === src)) return;\n\n const el = tag === \"video\" ? document.createElement(\"video\") : new Audio();\n el.preload = \"auto\";\n el.src = src;\n el.load();\n el.muted = this.muted;\n if (this.playbackRate !== 1) el.playbackRate = this.playbackRate;\n\n this._parentMedia.push({ el, start, duration });\n }\n\n /**\n * Set up a single parent-frame audio from an explicit URL (via `audio-src`).\n * Convenience for the common single-narration case — starts preloading\n * immediately without waiting for the iframe to load.\n */\n private _setupParentAudioFromUrl(audioSrc: string) {\n this._createParentMedia(audioSrc, \"audio\", 0, Infinity);\n }\n\n /**\n * Extract ALL timed media (audio/video with data-start) from the iframe's\n * DOM and create parent-frame copies. Disables the iframe originals so the\n * runtime doesn't try to play them (which would fail on mobile and cause\n * double playback on desktop).\n *\n * If `audio-src` was already set, this just disables the iframe media.\n */\n private _setupParentMedia() {\n try {\n const doc = this.iframe.contentDocument;\n if (!doc) return;\n\n // Find all timed media — matches the runtime's media.ts selector\n const mediaEls = doc.querySelectorAll<HTMLMediaElement>(\n \"audio[data-start], video[data-start]\",\n );\n\n for (const iframeEl of mediaEls) {\n const src = iframeEl.getAttribute(\"src\") || iframeEl.querySelector(\"source\")?.src;\n if (!src) continue;\n\n const start = parseFloat(iframeEl.getAttribute(\"data-start\") || \"0\");\n const duration = parseFloat(iframeEl.getAttribute(\"data-duration\") || \"Infinity\");\n const tag = iframeEl.tagName === \"VIDEO\" ? (\"video\" as const) : (\"audio\" as const);\n\n this._createParentMedia(src, tag, start, duration);\n\n // DO NOT strip data-start, data-duration, or src from the iframe elements.\n // The runtime's syncRuntimeMedia queries audio[data-start] — removing these\n // attributes makes the runtime unable to find, sync, or play media clips.\n // The iframe copies remain fully functional for the runtime. On mobile,\n // parent copies provide the audible output via the component's play() method.\n // On desktop and in the studio (which calls __player.play() directly),\n // the runtime's own media sync handles playback.\n }\n } catch {\n // Cross-origin iframe — can't access DOM, fall back to iframe media\n }\n }\n\n private _hidePoster() {\n this.posterEl?.remove();\n this.posterEl = null;\n }\n}\n\nif (!customElements.get(\"hyperframes-player\")) {\n customElements.define(\"hyperframes-player\", HyperframesPlayer);\n}\n\nexport { HyperframesPlayer };\nexport { formatTime, formatSpeed, SPEED_PRESETS } from \"./controls.js\";\nexport type { ControlsCallbacks, ControlsOptions } from \"./controls.js\";\n","export const PLAYER_STYLES = /* css */ `\n :host {\n display: block;\n position: relative;\n overflow: hidden;\n background: #000;\n contain: layout style;\n }\n\n .hfp-container {\n position: absolute;\n inset: 0;\n overflow: hidden;\n pointer-events: none;\n }\n\n\n .hfp-iframe {\n position: absolute;\n top: 50%;\n left: 50%;\n border: none;\n pointer-events: none;\n }\n\n .hfp-poster {\n position: absolute;\n inset: 0;\n object-fit: contain;\n z-index: 1;\n pointer-events: none;\n }\n\n /* ── Theming via CSS custom properties ──\n *\n * Override from outside the shadow DOM:\n * hyperframes-player {\n * --hfp-controls-bg: linear-gradient(transparent, rgba(0,0,0,0.9));\n * --hfp-accent: #ff6b6b;\n * --hfp-font: \"Inter\", sans-serif;\n * }\n */\n\n .hfp-controls {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n display: flex;\n align-items: center;\n gap: var(--hfp-controls-gap, 12px);\n padding: var(--hfp-controls-padding, 8px 16px);\n background: var(--hfp-controls-bg, linear-gradient(transparent, rgba(0, 0, 0, 0.7)));\n color: var(--hfp-color, #fff);\n font-family: var(--hfp-font, system-ui, -apple-system, sans-serif);\n font-size: var(--hfp-font-size, 13px);\n z-index: 10;\n pointer-events: auto;\n opacity: 1;\n transition: opacity 0.3s ease;\n user-select: none;\n }\n\n .hfp-controls.hfp-hidden {\n opacity: 0;\n pointer-events: none;\n }\n\n .hfp-play-btn {\n background: none;\n border: none;\n color: var(--hfp-color, #fff);\n cursor: pointer;\n padding: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n flex-shrink: 0;\n z-index: 10;\n }\n\n .hfp-play-btn:hover {\n opacity: 0.8;\n }\n\n .hfp-play-btn svg,\n .hfp-play-btn svg * {\n pointer-events: none;\n }\n\n .hfp-scrubber {\n flex: 1;\n height: var(--hfp-scrubber-height, 4px);\n background: var(--hfp-scrubber-bg, rgba(255, 255, 255, 0.3));\n border-radius: var(--hfp-scrubber-radius, 2px);\n cursor: pointer;\n position: relative;\n }\n\n .hfp-scrubber:hover {\n height: var(--hfp-scrubber-height-hover, 6px);\n }\n\n .hfp-progress {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n background: var(--hfp-accent, #fff);\n border-radius: var(--hfp-scrubber-radius, 2px);\n pointer-events: none;\n }\n\n .hfp-time {\n flex-shrink: 0;\n font-variant-numeric: tabular-nums;\n opacity: 0.9;\n }\n\n .hfp-speed-wrap {\n position: relative;\n flex-shrink: 0;\n }\n\n .hfp-speed-btn {\n background: var(--hfp-speed-btn-bg, rgba(255, 255, 255, 0.15));\n border: none;\n border-radius: var(--hfp-speed-btn-radius, 4px);\n color: var(--hfp-color, #fff);\n cursor: pointer;\n font-family: var(--hfp-font, system-ui, -apple-system, sans-serif);\n font-size: 12px;\n font-variant-numeric: tabular-nums;\n font-weight: 600;\n padding: 4px 8px;\n min-width: 40px;\n text-align: center;\n transition: background 0.15s ease;\n }\n\n .hfp-speed-btn:hover {\n background: var(--hfp-speed-btn-bg-hover, rgba(255, 255, 255, 0.3));\n }\n\n .hfp-speed-menu {\n position: absolute;\n bottom: calc(100% + 8px);\n right: 0;\n background: var(--hfp-menu-bg, rgba(20, 20, 20, 0.95));\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n border: 1px solid var(--hfp-menu-border, rgba(255, 255, 255, 0.1));\n border-radius: var(--hfp-menu-radius, 8px);\n padding: 4px;\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 80px;\n opacity: 0;\n visibility: hidden;\n transform: translateY(4px);\n transition: opacity 0.15s ease, transform 0.15s ease, visibility 0.15s;\n box-shadow: var(--hfp-menu-shadow, 0 8px 24px rgba(0, 0, 0, 0.4));\n }\n\n .hfp-speed-menu.hfp-open {\n opacity: 1;\n visibility: visible;\n transform: translateY(0);\n }\n\n .hfp-speed-option {\n background: none;\n border: none;\n border-radius: 4px;\n color: var(--hfp-menu-color, rgba(255, 255, 255, 0.7));\n cursor: pointer;\n font-family: var(--hfp-font, system-ui, -apple-system, sans-serif);\n font-size: 13px;\n font-variant-numeric: tabular-nums;\n padding: 6px 12px;\n text-align: left;\n transition: background 0.1s ease, color 0.1s ease;\n white-space: nowrap;\n }\n\n .hfp-speed-option:hover {\n background: var(--hfp-menu-hover-bg, rgba(255, 255, 255, 0.1));\n color: var(--hfp-color, #fff);\n }\n\n .hfp-speed-option.hfp-active {\n color: var(--hfp-accent, #fff);\n font-weight: 600;\n }\n`;\n\nexport const PLAY_ICON = `<svg width=\"24\" height=\"24\" viewBox=\"0 0 18 18\" fill=\"currentColor\"><polygon points=\"4,2 16,9 4,16\"/></svg>`;\nexport const PAUSE_ICON = `<svg width=\"24\" height=\"24\" viewBox=\"0 0 18 18\" fill=\"currentColor\"><rect x=\"3\" y=\"2\" width=\"4\" height=\"14\"/><rect x=\"11\" y=\"2\" width=\"4\" height=\"14\"/></svg>`;\n","import { PLAY_ICON, PAUSE_ICON } from \"./styles.js\";\n\nexport interface ControlsCallbacks {\n onPlay: () => void;\n onPause: () => void;\n onSeek: (fraction: number) => void;\n onSpeedChange: (speed: number) => void;\n}\n\n/** Default logarithmic speed presets — each step roughly doubles/halves. */\nexport const SPEED_PRESETS = [0.25, 0.5, 1, 1.5, 2, 4] as const;\n\nexport interface ControlsOptions {\n /** Speed presets shown in the menu. Defaults to SPEED_PRESETS. */\n speedPresets?: readonly number[];\n}\n\nexport function formatSpeed(speed: number): string {\n return Number.isInteger(speed) ? `${speed}x` : `${speed}x`;\n}\n\nexport function formatTime(seconds: number): string {\n // Handle non-finite values gracefully\n if (!Number.isFinite(seconds) || seconds < 0) {\n return \"0:00\";\n }\n const s = Math.floor(seconds);\n const m = Math.floor(s / 60);\n const sec = s % 60;\n return `${m}:${sec.toString().padStart(2, \"0\")}`;\n}\n\nexport function createControls(\n parent: ShadowRoot | HTMLElement,\n callbacks: ControlsCallbacks,\n options: ControlsOptions = {},\n): {\n updateTime: (current: number, duration: number) => void;\n updatePlaying: (playing: boolean) => void;\n updateSpeed: (speed: number) => void;\n show: () => void;\n hide: () => void;\n destroy: () => void;\n} {\n const presets = options.speedPresets ?? SPEED_PRESETS;\n\n const controls = document.createElement(\"div\");\n controls.className = \"hfp-controls\";\n // Keep overlay interactions from falling through to the host-level click toggle.\n controls.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n });\n\n const playBtn = document.createElement(\"button\");\n playBtn.className = \"hfp-play-btn\";\n playBtn.type = \"button\";\n playBtn.innerHTML = PLAY_ICON;\n playBtn.setAttribute(\"aria-label\", \"Play\");\n\n const scrubber = document.createElement(\"div\");\n scrubber.className = \"hfp-scrubber\";\n const progress = document.createElement(\"div\");\n progress.className = \"hfp-progress\";\n progress.style.width = \"0%\";\n scrubber.appendChild(progress);\n\n const time = document.createElement(\"span\");\n time.className = \"hfp-time\";\n time.textContent = \"0:00 / 0:00\";\n\n const speedWrap = document.createElement(\"div\");\n speedWrap.className = \"hfp-speed-wrap\";\n\n const speedBtn = document.createElement(\"button\");\n speedBtn.className = \"hfp-speed-btn\";\n speedBtn.type = \"button\";\n speedBtn.textContent = \"1x\";\n speedBtn.setAttribute(\"aria-label\", \"Playback speed\");\n\n const speedMenu = document.createElement(\"div\");\n speedMenu.className = \"hfp-speed-menu\";\n speedMenu.setAttribute(\"role\", \"menu\");\n for (const preset of presets) {\n const item = document.createElement(\"button\");\n item.className = \"hfp-speed-option\";\n item.type = \"button\";\n item.setAttribute(\"role\", \"menuitem\");\n item.dataset.speed = String(preset);\n item.textContent = formatSpeed(preset);\n if (preset === 1) item.classList.add(\"hfp-active\");\n speedMenu.appendChild(item);\n }\n\n speedWrap.appendChild(speedMenu);\n speedWrap.appendChild(speedBtn);\n\n controls.appendChild(playBtn);\n controls.appendChild(scrubber);\n controls.appendChild(time);\n controls.appendChild(speedWrap);\n parent.appendChild(controls);\n\n let isPlaying = false;\n let hideTimeout: ReturnType<typeof setTimeout> | null = null;\n let speedIndex = presets.indexOf(1); // start at 1x\n if (speedIndex === -1) speedIndex = 0;\n\n playBtn.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n if (isPlaying) callbacks.onPause();\n else callbacks.onPlay();\n });\n\n const setActiveOption = (speed: number) => {\n for (const opt of speedMenu.querySelectorAll(\".hfp-speed-option\")) {\n opt.classList.toggle(\"hfp-active\", (opt as HTMLElement).dataset.speed === String(speed));\n }\n };\n\n speedBtn.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n const isOpen = speedMenu.classList.toggle(\"hfp-open\");\n speedBtn.setAttribute(\"aria-expanded\", String(isOpen));\n });\n\n speedMenu.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n const target = (e.target as HTMLElement).closest(\".hfp-speed-option\") as HTMLElement | null;\n if (!target) return;\n const newSpeed = parseFloat(target.dataset.speed!);\n speedIndex = presets.indexOf(newSpeed);\n speedBtn.textContent = formatSpeed(newSpeed);\n setActiveOption(newSpeed);\n speedMenu.classList.remove(\"hfp-open\");\n speedBtn.setAttribute(\"aria-expanded\", \"false\");\n callbacks.onSpeedChange(newSpeed);\n });\n\n // Close menu when clicking outside\n const onDocClick = () => {\n speedMenu.classList.remove(\"hfp-open\");\n speedBtn.setAttribute(\"aria-expanded\", \"false\");\n };\n document.addEventListener(\"click\", onDocClick);\n\n const handleScrubAt = (clientX: number) => {\n const rect = scrubber.getBoundingClientRect();\n const fraction = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));\n callbacks.onSeek(fraction);\n };\n\n let scrubbing = false;\n\n scrubber.addEventListener(\"mousedown\", (e) => {\n e.stopPropagation();\n scrubbing = true;\n handleScrubAt(e.clientX);\n });\n const onMouseMove = (e: MouseEvent) => {\n if (scrubbing) handleScrubAt(e.clientX);\n };\n const onMouseUp = () => {\n scrubbing = false;\n };\n document.addEventListener(\"mousemove\", onMouseMove);\n document.addEventListener(\"mouseup\", onMouseUp);\n\n scrubber.addEventListener(\n \"touchstart\",\n (e) => {\n scrubbing = true;\n const touch = e.touches[0];\n if (touch) handleScrubAt(touch.clientX);\n },\n { passive: true },\n );\n const onTouchMove = (e: TouchEvent) => {\n if (scrubbing) {\n const touch = e.touches[0];\n if (touch) handleScrubAt(touch.clientX);\n }\n };\n const onTouchEnd = () => {\n scrubbing = false;\n };\n document.addEventListener(\"touchmove\", onTouchMove, { passive: true });\n document.addEventListener(\"touchend\", onTouchEnd);\n\n const startHideTimer = () => {\n if (hideTimeout) clearTimeout(hideTimeout);\n hideTimeout = setTimeout(() => {\n if (isPlaying) controls.classList.add(\"hfp-hidden\");\n }, 3000);\n };\n\n const host = parent instanceof ShadowRoot ? (parent.host as HTMLElement) : parent;\n host.addEventListener(\"mousemove\", () => {\n controls.classList.remove(\"hfp-hidden\");\n startHideTimer();\n });\n host.addEventListener(\"mouseleave\", () => {\n if (isPlaying) controls.classList.add(\"hfp-hidden\");\n });\n\n return {\n updateTime(current: number, duration: number) {\n const pct = duration > 0 ? (current / duration) * 100 : 0;\n progress.style.width = `${pct}%`;\n time.textContent = `${formatTime(current)} / ${formatTime(duration)}`;\n },\n updatePlaying(playing: boolean) {\n isPlaying = playing;\n playBtn.innerHTML = playing ? PAUSE_ICON : PLAY_ICON;\n playBtn.setAttribute(\"aria-label\", playing ? \"Pause\" : \"Play\");\n if (playing) startHideTimer();\n else controls.classList.remove(\"hfp-hidden\");\n },\n updateSpeed(speed: number) {\n const idx = presets.indexOf(speed);\n if (idx !== -1) speedIndex = idx;\n speedBtn.textContent = formatSpeed(speed);\n setActiveOption(speed);\n },\n show() {\n controls.style.display = \"\";\n },\n hide() {\n controls.style.display = \"none\";\n },\n destroy() {\n document.removeEventListener(\"mousemove\", onMouseMove);\n document.removeEventListener(\"mouseup\", onMouseUp);\n document.removeEventListener(\"touchmove\", onTouchMove);\n document.removeEventListener(\"touchend\", onTouchEnd);\n document.removeEventListener(\"click\", onDocClick);\n if (hideTimeout) clearTimeout(hideTimeout);\n },\n };\n}\n"],"mappings":"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","names":["hyperframes_player_exports","__export","HyperframesPlayer","SPEED_PRESETS","formatSpeed","formatTime","PLAYER_STYLES","PLAY_ICON","PAUSE_ICON","SPEED_PRESETS","formatSpeed","speed","formatTime","seconds","s","m","sec","createControls","parent","callbacks","options","presets","controls","e","playBtn","PLAY_ICON","scrubber","progress","time","speedWrap","speedBtn","speedMenu","preset","item","isPlaying","hideTimeout","speedIndex","setActiveOption","opt","isOpen","target","newSpeed","onDocClick","handleScrubAt","clientX","rect","fraction","scrubbing","onMouseMove","onMouseUp","touch","onTouchMove","onTouchEnd","startHideTimer","host","current","duration","pct","playing","PAUSE_ICON","idx","DEFAULT_FPS","RUNTIME_CDN_URL","HyperframesPlayer","style","PLAYER_STYLES","event","m","name","_old","val","rate","timeInSeconds","frame","relTime","t","r","l","action","extra","target","data","wasPlaying","now","attempts","win","hasRuntime","hasTimelines","adapter","keys","rootId","key","tl","root","w","h","doc","script","rect","scale","callbacks","fraction","speed","presetsAttr","speedPresets","n","createControls","url","mediaEls","el","src","tag","start","duration","audioSrc","iframeEl"]}
|
|
@@ -195,5 +195,5 @@ var R=`
|
|
|
195
195
|
color: var(--hfp-accent, #fff);
|
|
196
196
|
font-weight: 600;
|
|
197
197
|
}
|
|
198
|
-
`,E='<svg width="24" height="24" viewBox="0 0 18 18" fill="currentColor"><polygon points="4,2 16,9 4,16"/></svg>',N='<svg width="24" height="24" viewBox="0 0 18 18" fill="currentColor"><rect x="3" y="2" width="4" height="14"/><rect x="11" y="2" width="4" height="14"/></svg>';var O=[.25,.5,1,1.5,2,4];function y(c){return Number.isInteger(c)?`${c}x`:`${c}x`}function x(c){if(!Number.isFinite(c)||c<0)return"0:00";let e=Math.floor(c),t=Math.floor(e/60),i=e%60;return`${t}:${i.toString().padStart(2,"0")}`}function H(c,e,t={}){let i=t.speedPresets??O,s=document.createElement("div");s.className="hfp-controls",s.addEventListener("click",r=>{r.stopPropagation()});let o=document.createElement("button");o.className="hfp-play-btn",o.type="button",o.innerHTML=E,o.setAttribute("aria-label","Play");let p=document.createElement("div");p.className="hfp-scrubber";let l=document.createElement("div");l.className="hfp-progress",l.style.width="0%",p.appendChild(l);let d=document.createElement("span");d.className="hfp-time",d.textContent="0:00 / 0:00";let h=document.createElement("div");h.className="hfp-speed-wrap";let a=document.createElement("button");a.className="hfp-speed-btn",a.type="button",a.textContent="1x",a.setAttribute("aria-label","Playback speed");let u=document.createElement("div");u.className="hfp-speed-menu",u.setAttribute("role","menu");for(let r of i){let n=document.createElement("button");n.className="hfp-speed-option",n.type="button",n.setAttribute("role","menuitem"),n.dataset.speed=String(r),n.textContent=y(r),r===1&&n.classList.add("hfp-active"),u.appendChild(n)}h.appendChild(u),h.appendChild(a),s.appendChild(o),s.appendChild(p),s.appendChild(d),s.appendChild(h),c.appendChild(s);let v=!1,b=null,g=i.indexOf(1);g===-1&&(g=0),o.addEventListener("click",r=>{r.stopPropagation(),v?e.onPause():e.onPlay()});let k=r=>{for(let n of u.querySelectorAll(".hfp-speed-option"))n.classList.toggle("hfp-active",n.dataset.speed===String(r))};a.addEventListener("click",r=>{r.stopPropagation();let n=u.classList.toggle("hfp-open");a.setAttribute("aria-expanded",String(n))}),u.addEventListener("click",r=>{r.stopPropagation();let n=r.target.closest(".hfp-speed-option");if(!n)return;let m=parseFloat(n.dataset.speed);g=i.indexOf(m),a.textContent=y(m),k(m),u.classList.remove("hfp-open"),a.setAttribute("aria-expanded","false"),e.onSpeedChange(m)});let A=()=>{u.classList.remove("hfp-open"),a.setAttribute("aria-expanded","false")};document.addEventListener("click",A);let _=r=>{let n=p.getBoundingClientRect(),m=Math.max(0,Math.min(1,(r-n.left)/n.width));e.onSeek(m)},f=!1;p.addEventListener("mousedown",r=>{r.stopPropagation(),f=!0,_(r.clientX)});let M=r=>{f&&_(r.clientX)},P=()=>{f=!1};document.addEventListener("mousemove",M),document.addEventListener("mouseup",P),p.addEventListener("touchstart",r=>{f=!0;let n=r.touches[0];n&&_(n.clientX)},{passive:!0});let L=r=>{if(f){let n=r.touches[0];n&&_(n.clientX)}},T=()=>{f=!1};document.addEventListener("touchmove",L,{passive:!0}),document.addEventListener("touchend",T);let S=()=>{b&&clearTimeout(b),b=setTimeout(()=>{v&&s.classList.add("hfp-hidden")},3e3)},I=c instanceof ShadowRoot?c.host:c;return I.addEventListener("mousemove",()=>{s.classList.remove("hfp-hidden"),S()}),I.addEventListener("mouseleave",()=>{v&&s.classList.add("hfp-hidden")}),{updateTime(r,n){let m=n>0?r/n*100:0;l.style.width=`${m}%`,d.textContent=`${x(r)} / ${x(n)}`},updatePlaying(r){v=r,o.innerHTML=r?N:E,o.setAttribute("aria-label",r?"Pause":"Play"),r?S():s.classList.remove("hfp-hidden")},updateSpeed(r){let n=i.indexOf(r);n!==-1&&(g=n),a.textContent=y(r),k(r)},show(){s.style.display=""},hide(){s.style.display="none"},destroy(){document.removeEventListener("mousemove",M),document.removeEventListener("mouseup",P),document.removeEventListener("touchmove",L),document.removeEventListener("touchend",T),document.removeEventListener("click",A),b&&clearTimeout(b)}}}var w=30,D="https://cdn.jsdelivr.net/npm/@hyperframes/core/dist/hyperframe.runtime.iife.js",C=class extends HTMLElement{static get observedAttributes(){return["src","width","height","controls","muted","poster","playback-rate","audio-src"]}shadow;container;iframe;posterEl=null;controlsApi=null;resizeObserver;_ready=!1;_duration=0;_currentTime=0;_paused=!0;_compositionWidth=1920;_compositionHeight=1080;_probeInterval=null;_lastUpdateMs=0;_parentMedia=[];constructor(){super(),this.shadow=this.attachShadow({mode:"open"});let e=document.createElement("style");e.textContent=R,this.shadow.appendChild(e),this.container=document.createElement("div"),this.container.className="hfp-container",this.iframe=document.createElement("iframe"),this.iframe.className="hfp-iframe",this.iframe.sandbox.add("allow-scripts","allow-same-origin"),this.iframe.allow="autoplay; fullscreen",this.iframe.referrerPolicy="no-referrer",this.iframe.title="HyperFrames Composition",this.container.appendChild(this.iframe),this.shadow.appendChild(this.container),this.addEventListener("click",t=>{this._isControlsClick(t)||(this._paused?this.play():this.pause())}),this.resizeObserver=new ResizeObserver(()=>this._updateScale()),this._onMessage=this._onMessage.bind(this),this._onIframeLoad=this._onIframeLoad.bind(this)}connectedCallback(){this.resizeObserver.observe(this),window.addEventListener("message",this._onMessage),this.iframe.addEventListener("load",this._onIframeLoad),this.hasAttribute("controls")&&this._setupControls(),this.hasAttribute("poster")&&this._setupPoster(),this.hasAttribute("audio-src")&&this._setupParentAudioFromUrl(this.getAttribute("audio-src")),this.hasAttribute("src")&&(this.iframe.src=this.getAttribute("src"))}disconnectedCallback(){this.resizeObserver.disconnect(),window.removeEventListener("message",this._onMessage),this.iframe.removeEventListener("load",this._onIframeLoad),this._probeInterval&&clearInterval(this._probeInterval),this.controlsApi?.destroy();for(let e of this._parentMedia)e.el.pause(),e.el.src="";this._parentMedia=[]}attributeChangedCallback(e,t,i){switch(e){case"src":i&&(this._ready=!1,this.iframe.src=i);break;case"width":this._compositionWidth=parseInt(i||"1920",10),this._updateScale();break;case"height":this._compositionHeight=parseInt(i||"1080",10),this._updateScale();break;case"controls":i!==null?this._setupControls():(this.controlsApi?.destroy(),this.controlsApi=null);break;case"poster":this._setupPoster();break;case"playback-rate":{let s=parseFloat(i||"1");for(let o of this._parentMedia)o.el.playbackRate=s;this._sendControl("set-playback-rate",{playbackRate:s}),this.controlsApi?.updateSpeed(s),this.dispatchEvent(new Event("ratechange"));break}case"muted":for(let s of this._parentMedia)s.el.muted=i!==null;this._sendControl("set-muted",{muted:i!==null});break;case"audio-src":i&&this._setupParentAudioFromUrl(i);break}}get iframeElement(){return this.iframe}play(){this._hidePoster(),this._playParentMedia(),this._sendControl("play"),this._paused=!1,this.controlsApi?.updatePlaying(!0),this.dispatchEvent(new Event("play"))}pause(){this._pauseParentMedia(),this._sendControl("pause"),this._paused=!0,this.controlsApi?.updatePlaying(!1),this.dispatchEvent(new Event("pause"))}seek(e){let t=Math.round(e*w);this._sendControl("seek",{frame:t}),this._currentTime=e;for(let i of this._parentMedia){let s=e-i.start;s>=0&&s<i.duration&&(i.el.currentTime=s)}this._paused=!0,this.controlsApi?.updatePlaying(!1),this.controlsApi?.updateTime(this._currentTime,this._duration)}get currentTime(){return this._currentTime}set currentTime(e){this.seek(e)}get duration(){return this._duration}get paused(){return this._paused}get ready(){return this._ready}get playbackRate(){return parseFloat(this.getAttribute("playback-rate")||"1")}set playbackRate(e){this.setAttribute("playback-rate",String(e))}get muted(){return this.hasAttribute("muted")}set muted(e){e?this.setAttribute("muted",""):this.removeAttribute("muted")}get loop(){return this.hasAttribute("loop")}set loop(e){e?this.setAttribute("loop",""):this.removeAttribute("loop")}_sendControl(e,t={}){try{this.iframe.contentWindow?.postMessage({source:"hf-parent",type:"control",action:e,...t},"*")}catch{}}_isControlsClick(e){return e.composedPath().some(t=>t instanceof HTMLElement&&t.classList.contains("hfp-controls"))}_onMessage(e){if(e.source!==this.iframe.contentWindow)return;let t=e.data;if(!(!t||t.source!=="hf-preview")){if(t.type==="state"){this._currentTime=(t.frame??0)/w;let i=!this._paused;this._paused=!t.isPlaying,i&&this._paused?this._pauseParentMedia():!i&&!this._paused&&this._playParentMedia();let s=performance.now();(s-this._lastUpdateMs>100||this._paused!==i)&&(this._lastUpdateMs=s,this.controlsApi?.updateTime(this._currentTime,this._duration),this.controlsApi?.updatePlaying(!this._paused),this.dispatchEvent(new CustomEvent("timeupdate",{detail:{currentTime:this._currentTime}}))),this._currentTime>=this._duration&&!this._paused&&(this._pauseParentMedia(),this.loop?(this.seek(0),this.play()):(this._paused=!0,this.controlsApi?.updatePlaying(!1),this.dispatchEvent(new Event("ended"))))}t.type==="timeline"&&t.durationInFrames>0&&Number.isFinite(t.durationInFrames)&&(this._duration=t.durationInFrames/w,this.controlsApi?.updateTime(this._currentTime,this._duration)),t.type==="stage-size"&&t.width>0&&t.height>0&&(this._compositionWidth=t.width,this._compositionHeight=t.height,this._updateScale())}}_runtimeInjected=!1;_onIframeLoad(){let e=0;this._runtimeInjected=!1,this._probeInterval&&clearInterval(this._probeInterval),this._probeInterval=setInterval(()=>{e++;try{let t=this.iframe.contentWindow;if(!t)return;let i=!!(t.__hf||t.__player),s=!!(t.__timelines&&Object.keys(t.__timelines).length>0);if(!i&&s&&!this._runtimeInjected&&e>=5){this._injectRuntime();return}if(this._runtimeInjected&&!i)return;let p=(()=>{if(t.__player&&typeof t.__player.getDuration=="function")return t.__player;if(t.__timelines){let l=Object.keys(t.__timelines);if(l.length>0){let d=this.iframe.contentDocument?.querySelector("[data-composition-id]")?.getAttribute("data-composition-id"),h=d&&d in t.__timelines?d:l[l.length-1],a=t.__timelines[h];return{getDuration:()=>a.duration()}}}return null})();if(p&&p.getDuration()>0){clearInterval(this._probeInterval),this._duration=p.getDuration(),this._ready=!0,this.controlsApi?.updateTime(0,this._duration),this.dispatchEvent(new CustomEvent("ready",{detail:{duration:this._duration}}));let d=this.iframe.contentDocument?.querySelector("[data-composition-id]");if(d){let h=parseInt(d.getAttribute("data-width")||"0",10),a=parseInt(d.getAttribute("data-height")||"0",10);h>0&&a>0&&(this._compositionWidth=h,this._compositionHeight=a,this._updateScale())}this._setupParentMedia(),this.hasAttribute("autoplay")&&this.play();return}}catch{}e>=40&&(clearInterval(this._probeInterval),this.dispatchEvent(new CustomEvent("error",{detail:{message:"Composition timeline not found after 8s"}})))},200)}_injectRuntime(){this._runtimeInjected=!0;try{let e=this.iframe.contentDocument;if(!e)return;let t=e.createElement("script");t.src=D,t.onload=()=>{},t.onerror=()=>{},(e.head||e.documentElement).appendChild(t)}catch{}}_updateScale(){let e=this.getBoundingClientRect();if(e.width===0||e.height===0)return;let t=Math.min(e.width/this._compositionWidth,e.height/this._compositionHeight);this.iframe.style.width=`${this._compositionWidth}px`,this.iframe.style.height=`${this._compositionHeight}px`,this.iframe.style.transform=`translate(-50%, -50%) scale(${t})`}_setupControls(){if(this.controlsApi)return;let e={onPlay:()=>this.play(),onPause:()=>this.pause(),onSeek:s=>this.seek(s*this._duration),onSpeedChange:s=>{this.playbackRate=s}},t=this.getAttribute("speed-presets"),i=t?t.split(",").map(Number).filter(s=>!isNaN(s)&&s>0):void 0;this.controlsApi=H(this.shadow,e,{speedPresets:i})}_setupPoster(){let e=this.getAttribute("poster");if(!e){this.posterEl?.remove(),this.posterEl=null;return}this.posterEl||(this.posterEl=document.createElement("img"),this.posterEl.className="hfp-poster",this.shadow.appendChild(this.posterEl)),this.posterEl.src=e}_playParentMedia(){for(let e of this._parentMedia)e.el.src&&e.el.play().catch(()=>{})}_pauseParentMedia(){for(let e of this._parentMedia)e.el.pause()}_createParentMedia(e,t,i,s){if(this._parentMedia.some(p=>p.el.src===e))return;let o=t==="video"?document.createElement("video"):new Audio;o.preload="auto",o.src=e,o.load(),o.muted=this.muted,this.playbackRate!==1&&(o.playbackRate=this.playbackRate),this._parentMedia.push({el:o,start:i,duration:s})}_setupParentAudioFromUrl(e){this._createParentMedia(e,"audio",0,1/0)}_setupParentMedia(){try{let e=this.iframe.contentDocument;if(!e)return;let t=e.querySelectorAll("audio[data-start], video[data-start]");for(let i of t){let s=i.getAttribute("src")||i.querySelector("source")?.src;if(!s)continue;let o=parseFloat(i.getAttribute("data-start")||"0"),p=parseFloat(i.getAttribute("data-duration")||"Infinity"),l=i.tagName==="VIDEO"?"video":"audio";this._createParentMedia(s,l,o,p),i.removeAttribute("src"),i.removeAttribute("data-start"),i.removeAttribute("data-duration"),i.querySelectorAll("source").forEach(d=>d.remove())}}catch{}}_hidePoster(){this.posterEl?.remove(),this.posterEl=null}};customElements.get("hyperframes-player")||customElements.define("hyperframes-player",C);export{C as HyperframesPlayer,O as SPEED_PRESETS,y as formatSpeed,x as formatTime};
|
|
198
|
+
`,E='<svg width="24" height="24" viewBox="0 0 18 18" fill="currentColor"><polygon points="4,2 16,9 4,16"/></svg>',N='<svg width="24" height="24" viewBox="0 0 18 18" fill="currentColor"><rect x="3" y="2" width="4" height="14"/><rect x="11" y="2" width="4" height="14"/></svg>';var H=[.25,.5,1,1.5,2,4];function y(d){return Number.isInteger(d)?`${d}x`:`${d}x`}function x(d){if(!Number.isFinite(d)||d<0)return"0:00";let e=Math.floor(d),t=Math.floor(e/60),i=e%60;return`${t}:${i.toString().padStart(2,"0")}`}function O(d,e,t={}){let i=t.speedPresets??H,s=document.createElement("div");s.className="hfp-controls",s.addEventListener("click",r=>{r.stopPropagation()});let o=document.createElement("button");o.className="hfp-play-btn",o.type="button",o.innerHTML=E,o.setAttribute("aria-label","Play");let p=document.createElement("div");p.className="hfp-scrubber";let l=document.createElement("div");l.className="hfp-progress",l.style.width="0%",p.appendChild(l);let c=document.createElement("span");c.className="hfp-time",c.textContent="0:00 / 0:00";let h=document.createElement("div");h.className="hfp-speed-wrap";let a=document.createElement("button");a.className="hfp-speed-btn",a.type="button",a.textContent="1x",a.setAttribute("aria-label","Playback speed");let u=document.createElement("div");u.className="hfp-speed-menu",u.setAttribute("role","menu");for(let r of i){let n=document.createElement("button");n.className="hfp-speed-option",n.type="button",n.setAttribute("role","menuitem"),n.dataset.speed=String(r),n.textContent=y(r),r===1&&n.classList.add("hfp-active"),u.appendChild(n)}h.appendChild(u),h.appendChild(a),s.appendChild(o),s.appendChild(p),s.appendChild(c),s.appendChild(h),d.appendChild(s);let v=!1,b=null,g=i.indexOf(1);g===-1&&(g=0),o.addEventListener("click",r=>{r.stopPropagation(),v?e.onPause():e.onPlay()});let k=r=>{for(let n of u.querySelectorAll(".hfp-speed-option"))n.classList.toggle("hfp-active",n.dataset.speed===String(r))};a.addEventListener("click",r=>{r.stopPropagation();let n=u.classList.toggle("hfp-open");a.setAttribute("aria-expanded",String(n))}),u.addEventListener("click",r=>{r.stopPropagation();let n=r.target.closest(".hfp-speed-option");if(!n)return;let m=parseFloat(n.dataset.speed);g=i.indexOf(m),a.textContent=y(m),k(m),u.classList.remove("hfp-open"),a.setAttribute("aria-expanded","false"),e.onSpeedChange(m)});let A=()=>{u.classList.remove("hfp-open"),a.setAttribute("aria-expanded","false")};document.addEventListener("click",A);let _=r=>{let n=p.getBoundingClientRect(),m=Math.max(0,Math.min(1,(r-n.left)/n.width));e.onSeek(m)},f=!1;p.addEventListener("mousedown",r=>{r.stopPropagation(),f=!0,_(r.clientX)});let M=r=>{f&&_(r.clientX)},L=()=>{f=!1};document.addEventListener("mousemove",M),document.addEventListener("mouseup",L),p.addEventListener("touchstart",r=>{f=!0;let n=r.touches[0];n&&_(n.clientX)},{passive:!0});let P=r=>{if(f){let n=r.touches[0];n&&_(n.clientX)}},T=()=>{f=!1};document.addEventListener("touchmove",P,{passive:!0}),document.addEventListener("touchend",T);let S=()=>{b&&clearTimeout(b),b=setTimeout(()=>{v&&s.classList.add("hfp-hidden")},3e3)},I=d instanceof ShadowRoot?d.host:d;return I.addEventListener("mousemove",()=>{s.classList.remove("hfp-hidden"),S()}),I.addEventListener("mouseleave",()=>{v&&s.classList.add("hfp-hidden")}),{updateTime(r,n){let m=n>0?r/n*100:0;l.style.width=`${m}%`,c.textContent=`${x(r)} / ${x(n)}`},updatePlaying(r){v=r,o.innerHTML=r?N:E,o.setAttribute("aria-label",r?"Pause":"Play"),r?S():s.classList.remove("hfp-hidden")},updateSpeed(r){let n=i.indexOf(r);n!==-1&&(g=n),a.textContent=y(r),k(r)},show(){s.style.display=""},hide(){s.style.display="none"},destroy(){document.removeEventListener("mousemove",M),document.removeEventListener("mouseup",L),document.removeEventListener("touchmove",P),document.removeEventListener("touchend",T),document.removeEventListener("click",A),b&&clearTimeout(b)}}}var w=30,D="https://cdn.jsdelivr.net/npm/@hyperframes/core/dist/hyperframe.runtime.iife.js",C=class extends HTMLElement{static get observedAttributes(){return["src","width","height","controls","muted","poster","playback-rate","audio-src"]}shadow;container;iframe;posterEl=null;controlsApi=null;resizeObserver;_ready=!1;_duration=0;_currentTime=0;_paused=!0;_compositionWidth=1920;_compositionHeight=1080;_probeInterval=null;_lastUpdateMs=0;_parentMedia=[];constructor(){super(),this.shadow=this.attachShadow({mode:"open"});let e=document.createElement("style");e.textContent=R,this.shadow.appendChild(e),this.container=document.createElement("div"),this.container.className="hfp-container",this.iframe=document.createElement("iframe"),this.iframe.className="hfp-iframe",this.iframe.sandbox.add("allow-scripts","allow-same-origin"),this.iframe.allow="autoplay; fullscreen",this.iframe.referrerPolicy="no-referrer",this.iframe.title="HyperFrames Composition",this.container.appendChild(this.iframe),this.shadow.appendChild(this.container),this.addEventListener("click",t=>{this._isControlsClick(t)||(this._paused?this.play():this.pause())}),this.resizeObserver=new ResizeObserver(()=>this._updateScale()),this._onMessage=this._onMessage.bind(this),this._onIframeLoad=this._onIframeLoad.bind(this)}connectedCallback(){this.resizeObserver.observe(this),window.addEventListener("message",this._onMessage),this.iframe.addEventListener("load",this._onIframeLoad),this.hasAttribute("controls")&&this._setupControls(),this.hasAttribute("poster")&&this._setupPoster(),this.hasAttribute("audio-src")&&this._setupParentAudioFromUrl(this.getAttribute("audio-src")),this.hasAttribute("src")&&(this.iframe.src=this.getAttribute("src"))}disconnectedCallback(){this.resizeObserver.disconnect(),window.removeEventListener("message",this._onMessage),this.iframe.removeEventListener("load",this._onIframeLoad),this._probeInterval&&clearInterval(this._probeInterval),this.controlsApi?.destroy();for(let e of this._parentMedia)e.el.pause(),e.el.src="";this._parentMedia=[]}attributeChangedCallback(e,t,i){switch(e){case"src":i&&(this._ready=!1,this.iframe.src=i);break;case"width":this._compositionWidth=parseInt(i||"1920",10),this._updateScale();break;case"height":this._compositionHeight=parseInt(i||"1080",10),this._updateScale();break;case"controls":i!==null?this._setupControls():(this.controlsApi?.destroy(),this.controlsApi=null);break;case"poster":this._setupPoster();break;case"playback-rate":{let s=parseFloat(i||"1");for(let o of this._parentMedia)o.el.playbackRate=s;this._sendControl("set-playback-rate",{playbackRate:s}),this.controlsApi?.updateSpeed(s),this.dispatchEvent(new Event("ratechange"));break}case"muted":for(let s of this._parentMedia)s.el.muted=i!==null;this._sendControl("set-muted",{muted:i!==null});break;case"audio-src":i&&this._setupParentAudioFromUrl(i);break}}get iframeElement(){return this.iframe}play(){this._hidePoster(),this._playParentMedia(),this._sendControl("play"),this._paused=!1,this.controlsApi?.updatePlaying(!0),this.dispatchEvent(new Event("play"))}pause(){this._pauseParentMedia(),this._sendControl("pause"),this._paused=!0,this.controlsApi?.updatePlaying(!1),this.dispatchEvent(new Event("pause"))}seek(e){let t=Math.round(e*w);this._sendControl("seek",{frame:t}),this._currentTime=e;for(let i of this._parentMedia){let s=e-i.start;s>=0&&s<i.duration&&(i.el.currentTime=s)}this._paused=!0,this.controlsApi?.updatePlaying(!1),this.controlsApi?.updateTime(this._currentTime,this._duration)}get currentTime(){return this._currentTime}set currentTime(e){this.seek(e)}get duration(){return this._duration}get paused(){return this._paused}get ready(){return this._ready}get playbackRate(){return parseFloat(this.getAttribute("playback-rate")||"1")}set playbackRate(e){this.setAttribute("playback-rate",String(e))}get muted(){return this.hasAttribute("muted")}set muted(e){e?this.setAttribute("muted",""):this.removeAttribute("muted")}get loop(){return this.hasAttribute("loop")}set loop(e){e?this.setAttribute("loop",""):this.removeAttribute("loop")}_sendControl(e,t={}){try{this.iframe.contentWindow?.postMessage({source:"hf-parent",type:"control",action:e,...t},"*")}catch{}}_isControlsClick(e){return e.composedPath().some(t=>t instanceof HTMLElement&&t.classList.contains("hfp-controls"))}_onMessage(e){if(e.source!==this.iframe.contentWindow)return;let t=e.data;if(!(!t||t.source!=="hf-preview")){if(t.type==="state"){this._currentTime=(t.frame??0)/w;let i=!this._paused;this._paused=!t.isPlaying,i&&this._paused?this._pauseParentMedia():!i&&!this._paused&&this._playParentMedia();let s=performance.now();(s-this._lastUpdateMs>100||this._paused!==i)&&(this._lastUpdateMs=s,this.controlsApi?.updateTime(this._currentTime,this._duration),this.controlsApi?.updatePlaying(!this._paused),this.dispatchEvent(new CustomEvent("timeupdate",{detail:{currentTime:this._currentTime}}))),this._currentTime>=this._duration&&!this._paused&&(this._pauseParentMedia(),this.loop?(this.seek(0),this.play()):(this._paused=!0,this.controlsApi?.updatePlaying(!1),this.dispatchEvent(new Event("ended"))))}t.type==="timeline"&&t.durationInFrames>0&&Number.isFinite(t.durationInFrames)&&(this._duration=t.durationInFrames/w,this.controlsApi?.updateTime(this._currentTime,this._duration)),t.type==="stage-size"&&t.width>0&&t.height>0&&(this._compositionWidth=t.width,this._compositionHeight=t.height,this._updateScale())}}_runtimeInjected=!1;_onIframeLoad(){let e=0;this._runtimeInjected=!1,this._probeInterval&&clearInterval(this._probeInterval),this._probeInterval=setInterval(()=>{e++;try{let t=this.iframe.contentWindow;if(!t)return;let i=!!(t.__hf||t.__player),s=!!(t.__timelines&&Object.keys(t.__timelines).length>0);if(!i&&s&&!this._runtimeInjected&&e>=5){this._injectRuntime();return}if(this._runtimeInjected&&!i)return;let p=(()=>{if(t.__player&&typeof t.__player.getDuration=="function")return t.__player;if(t.__timelines){let l=Object.keys(t.__timelines);if(l.length>0){let c=this.iframe.contentDocument?.querySelector("[data-composition-id]")?.getAttribute("data-composition-id"),h=c&&c in t.__timelines?c:l[l.length-1],a=t.__timelines[h];return{getDuration:()=>a.duration()}}}return null})();if(p&&p.getDuration()>0){clearInterval(this._probeInterval),this._duration=p.getDuration(),this._ready=!0,this.controlsApi?.updateTime(0,this._duration),this.dispatchEvent(new CustomEvent("ready",{detail:{duration:this._duration}}));let c=this.iframe.contentDocument?.querySelector("[data-composition-id]");if(c){let h=parseInt(c.getAttribute("data-width")||"0",10),a=parseInt(c.getAttribute("data-height")||"0",10);h>0&&a>0&&(this._compositionWidth=h,this._compositionHeight=a,this._updateScale())}this._setupParentMedia(),this.hasAttribute("autoplay")&&this.play();return}}catch{}e>=40&&(clearInterval(this._probeInterval),this.dispatchEvent(new CustomEvent("error",{detail:{message:"Composition timeline not found after 8s"}})))},200)}_injectRuntime(){this._runtimeInjected=!0;try{let e=this.iframe.contentDocument;if(!e)return;let t=e.createElement("script");t.src=D,t.onload=()=>{},t.onerror=()=>{},(e.head||e.documentElement).appendChild(t)}catch{}}_updateScale(){let e=this.getBoundingClientRect();if(e.width===0||e.height===0)return;let t=Math.min(e.width/this._compositionWidth,e.height/this._compositionHeight);this.iframe.style.width=`${this._compositionWidth}px`,this.iframe.style.height=`${this._compositionHeight}px`,this.iframe.style.transform=`translate(-50%, -50%) scale(${t})`}_setupControls(){if(this.controlsApi)return;let e={onPlay:()=>this.play(),onPause:()=>this.pause(),onSeek:s=>this.seek(s*this._duration),onSpeedChange:s=>{this.playbackRate=s}},t=this.getAttribute("speed-presets"),i=t?t.split(",").map(Number).filter(s=>!isNaN(s)&&s>0):void 0;this.controlsApi=O(this.shadow,e,{speedPresets:i})}_setupPoster(){let e=this.getAttribute("poster");if(!e){this.posterEl?.remove(),this.posterEl=null;return}this.posterEl||(this.posterEl=document.createElement("img"),this.posterEl.className="hfp-poster",this.shadow.appendChild(this.posterEl)),this.posterEl.src=e}_playParentMedia(){for(let e of this._parentMedia)e.el.src&&e.el.play().then(()=>{this._muteIframeMedia()}).catch(()=>{})}_muteIframeMedia(){try{let e=this.iframe.contentDocument;if(!e)return;let t=e.querySelectorAll("audio[data-start], video[data-start]");for(let i of t)i.volume=0}catch{}}_pauseParentMedia(){for(let e of this._parentMedia)e.el.pause()}_createParentMedia(e,t,i,s){if(this._parentMedia.some(p=>p.el.src===e))return;let o=t==="video"?document.createElement("video"):new Audio;o.preload="auto",o.src=e,o.load(),o.muted=this.muted,this.playbackRate!==1&&(o.playbackRate=this.playbackRate),this._parentMedia.push({el:o,start:i,duration:s})}_setupParentAudioFromUrl(e){this._createParentMedia(e,"audio",0,1/0)}_setupParentMedia(){try{let e=this.iframe.contentDocument;if(!e)return;let t=e.querySelectorAll("audio[data-start], video[data-start]");for(let i of t){let s=i.getAttribute("src")||i.querySelector("source")?.src;if(!s)continue;let o=parseFloat(i.getAttribute("data-start")||"0"),p=parseFloat(i.getAttribute("data-duration")||"Infinity"),l=i.tagName==="VIDEO"?"video":"audio";this._createParentMedia(s,l,o,p)}}catch{}}_hidePoster(){this.posterEl?.remove(),this.posterEl=null}};customElements.get("hyperframes-player")||customElements.define("hyperframes-player",C);export{C as HyperframesPlayer,H as SPEED_PRESETS,y as formatSpeed,x as formatTime};
|
|
199
199
|
//# sourceMappingURL=hyperframes-player.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/styles.ts","../src/controls.ts","../src/hyperframes-player.ts"],"sourcesContent":["export const PLAYER_STYLES = /* css */ `\n :host {\n display: block;\n position: relative;\n overflow: hidden;\n background: #000;\n contain: layout style;\n }\n\n .hfp-container {\n position: absolute;\n inset: 0;\n overflow: hidden;\n pointer-events: none;\n }\n\n\n .hfp-iframe {\n position: absolute;\n top: 50%;\n left: 50%;\n border: none;\n pointer-events: none;\n }\n\n .hfp-poster {\n position: absolute;\n inset: 0;\n object-fit: contain;\n z-index: 1;\n pointer-events: none;\n }\n\n /* ── Theming via CSS custom properties ──\n *\n * Override from outside the shadow DOM:\n * hyperframes-player {\n * --hfp-controls-bg: linear-gradient(transparent, rgba(0,0,0,0.9));\n * --hfp-accent: #ff6b6b;\n * --hfp-font: \"Inter\", sans-serif;\n * }\n */\n\n .hfp-controls {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n display: flex;\n align-items: center;\n gap: var(--hfp-controls-gap, 12px);\n padding: var(--hfp-controls-padding, 8px 16px);\n background: var(--hfp-controls-bg, linear-gradient(transparent, rgba(0, 0, 0, 0.7)));\n color: var(--hfp-color, #fff);\n font-family: var(--hfp-font, system-ui, -apple-system, sans-serif);\n font-size: var(--hfp-font-size, 13px);\n z-index: 10;\n pointer-events: auto;\n opacity: 1;\n transition: opacity 0.3s ease;\n user-select: none;\n }\n\n .hfp-controls.hfp-hidden {\n opacity: 0;\n pointer-events: none;\n }\n\n .hfp-play-btn {\n background: none;\n border: none;\n color: var(--hfp-color, #fff);\n cursor: pointer;\n padding: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n flex-shrink: 0;\n z-index: 10;\n }\n\n .hfp-play-btn:hover {\n opacity: 0.8;\n }\n\n .hfp-play-btn svg,\n .hfp-play-btn svg * {\n pointer-events: none;\n }\n\n .hfp-scrubber {\n flex: 1;\n height: var(--hfp-scrubber-height, 4px);\n background: var(--hfp-scrubber-bg, rgba(255, 255, 255, 0.3));\n border-radius: var(--hfp-scrubber-radius, 2px);\n cursor: pointer;\n position: relative;\n }\n\n .hfp-scrubber:hover {\n height: var(--hfp-scrubber-height-hover, 6px);\n }\n\n .hfp-progress {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n background: var(--hfp-accent, #fff);\n border-radius: var(--hfp-scrubber-radius, 2px);\n pointer-events: none;\n }\n\n .hfp-time {\n flex-shrink: 0;\n font-variant-numeric: tabular-nums;\n opacity: 0.9;\n }\n\n .hfp-speed-wrap {\n position: relative;\n flex-shrink: 0;\n }\n\n .hfp-speed-btn {\n background: var(--hfp-speed-btn-bg, rgba(255, 255, 255, 0.15));\n border: none;\n border-radius: var(--hfp-speed-btn-radius, 4px);\n color: var(--hfp-color, #fff);\n cursor: pointer;\n font-family: var(--hfp-font, system-ui, -apple-system, sans-serif);\n font-size: 12px;\n font-variant-numeric: tabular-nums;\n font-weight: 600;\n padding: 4px 8px;\n min-width: 40px;\n text-align: center;\n transition: background 0.15s ease;\n }\n\n .hfp-speed-btn:hover {\n background: var(--hfp-speed-btn-bg-hover, rgba(255, 255, 255, 0.3));\n }\n\n .hfp-speed-menu {\n position: absolute;\n bottom: calc(100% + 8px);\n right: 0;\n background: var(--hfp-menu-bg, rgba(20, 20, 20, 0.95));\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n border: 1px solid var(--hfp-menu-border, rgba(255, 255, 255, 0.1));\n border-radius: var(--hfp-menu-radius, 8px);\n padding: 4px;\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 80px;\n opacity: 0;\n visibility: hidden;\n transform: translateY(4px);\n transition: opacity 0.15s ease, transform 0.15s ease, visibility 0.15s;\n box-shadow: var(--hfp-menu-shadow, 0 8px 24px rgba(0, 0, 0, 0.4));\n }\n\n .hfp-speed-menu.hfp-open {\n opacity: 1;\n visibility: visible;\n transform: translateY(0);\n }\n\n .hfp-speed-option {\n background: none;\n border: none;\n border-radius: 4px;\n color: var(--hfp-menu-color, rgba(255, 255, 255, 0.7));\n cursor: pointer;\n font-family: var(--hfp-font, system-ui, -apple-system, sans-serif);\n font-size: 13px;\n font-variant-numeric: tabular-nums;\n padding: 6px 12px;\n text-align: left;\n transition: background 0.1s ease, color 0.1s ease;\n white-space: nowrap;\n }\n\n .hfp-speed-option:hover {\n background: var(--hfp-menu-hover-bg, rgba(255, 255, 255, 0.1));\n color: var(--hfp-color, #fff);\n }\n\n .hfp-speed-option.hfp-active {\n color: var(--hfp-accent, #fff);\n font-weight: 600;\n }\n`;\n\nexport const PLAY_ICON = `<svg width=\"24\" height=\"24\" viewBox=\"0 0 18 18\" fill=\"currentColor\"><polygon points=\"4,2 16,9 4,16\"/></svg>`;\nexport const PAUSE_ICON = `<svg width=\"24\" height=\"24\" viewBox=\"0 0 18 18\" fill=\"currentColor\"><rect x=\"3\" y=\"2\" width=\"4\" height=\"14\"/><rect x=\"11\" y=\"2\" width=\"4\" height=\"14\"/></svg>`;\n","import { PLAY_ICON, PAUSE_ICON } from \"./styles.js\";\n\nexport interface ControlsCallbacks {\n onPlay: () => void;\n onPause: () => void;\n onSeek: (fraction: number) => void;\n onSpeedChange: (speed: number) => void;\n}\n\n/** Default logarithmic speed presets — each step roughly doubles/halves. */\nexport const SPEED_PRESETS = [0.25, 0.5, 1, 1.5, 2, 4] as const;\n\nexport interface ControlsOptions {\n /** Speed presets shown in the menu. Defaults to SPEED_PRESETS. */\n speedPresets?: readonly number[];\n}\n\nexport function formatSpeed(speed: number): string {\n return Number.isInteger(speed) ? `${speed}x` : `${speed}x`;\n}\n\nexport function formatTime(seconds: number): string {\n // Handle non-finite values gracefully\n if (!Number.isFinite(seconds) || seconds < 0) {\n return \"0:00\";\n }\n const s = Math.floor(seconds);\n const m = Math.floor(s / 60);\n const sec = s % 60;\n return `${m}:${sec.toString().padStart(2, \"0\")}`;\n}\n\nexport function createControls(\n parent: ShadowRoot | HTMLElement,\n callbacks: ControlsCallbacks,\n options: ControlsOptions = {},\n): {\n updateTime: (current: number, duration: number) => void;\n updatePlaying: (playing: boolean) => void;\n updateSpeed: (speed: number) => void;\n show: () => void;\n hide: () => void;\n destroy: () => void;\n} {\n const presets = options.speedPresets ?? SPEED_PRESETS;\n\n const controls = document.createElement(\"div\");\n controls.className = \"hfp-controls\";\n // Keep overlay interactions from falling through to the host-level click toggle.\n controls.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n });\n\n const playBtn = document.createElement(\"button\");\n playBtn.className = \"hfp-play-btn\";\n playBtn.type = \"button\";\n playBtn.innerHTML = PLAY_ICON;\n playBtn.setAttribute(\"aria-label\", \"Play\");\n\n const scrubber = document.createElement(\"div\");\n scrubber.className = \"hfp-scrubber\";\n const progress = document.createElement(\"div\");\n progress.className = \"hfp-progress\";\n progress.style.width = \"0%\";\n scrubber.appendChild(progress);\n\n const time = document.createElement(\"span\");\n time.className = \"hfp-time\";\n time.textContent = \"0:00 / 0:00\";\n\n const speedWrap = document.createElement(\"div\");\n speedWrap.className = \"hfp-speed-wrap\";\n\n const speedBtn = document.createElement(\"button\");\n speedBtn.className = \"hfp-speed-btn\";\n speedBtn.type = \"button\";\n speedBtn.textContent = \"1x\";\n speedBtn.setAttribute(\"aria-label\", \"Playback speed\");\n\n const speedMenu = document.createElement(\"div\");\n speedMenu.className = \"hfp-speed-menu\";\n speedMenu.setAttribute(\"role\", \"menu\");\n for (const preset of presets) {\n const item = document.createElement(\"button\");\n item.className = \"hfp-speed-option\";\n item.type = \"button\";\n item.setAttribute(\"role\", \"menuitem\");\n item.dataset.speed = String(preset);\n item.textContent = formatSpeed(preset);\n if (preset === 1) item.classList.add(\"hfp-active\");\n speedMenu.appendChild(item);\n }\n\n speedWrap.appendChild(speedMenu);\n speedWrap.appendChild(speedBtn);\n\n controls.appendChild(playBtn);\n controls.appendChild(scrubber);\n controls.appendChild(time);\n controls.appendChild(speedWrap);\n parent.appendChild(controls);\n\n let isPlaying = false;\n let hideTimeout: ReturnType<typeof setTimeout> | null = null;\n let speedIndex = presets.indexOf(1); // start at 1x\n if (speedIndex === -1) speedIndex = 0;\n\n playBtn.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n if (isPlaying) callbacks.onPause();\n else callbacks.onPlay();\n });\n\n const setActiveOption = (speed: number) => {\n for (const opt of speedMenu.querySelectorAll(\".hfp-speed-option\")) {\n opt.classList.toggle(\"hfp-active\", (opt as HTMLElement).dataset.speed === String(speed));\n }\n };\n\n speedBtn.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n const isOpen = speedMenu.classList.toggle(\"hfp-open\");\n speedBtn.setAttribute(\"aria-expanded\", String(isOpen));\n });\n\n speedMenu.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n const target = (e.target as HTMLElement).closest(\".hfp-speed-option\") as HTMLElement | null;\n if (!target) return;\n const newSpeed = parseFloat(target.dataset.speed!);\n speedIndex = presets.indexOf(newSpeed);\n speedBtn.textContent = formatSpeed(newSpeed);\n setActiveOption(newSpeed);\n speedMenu.classList.remove(\"hfp-open\");\n speedBtn.setAttribute(\"aria-expanded\", \"false\");\n callbacks.onSpeedChange(newSpeed);\n });\n\n // Close menu when clicking outside\n const onDocClick = () => {\n speedMenu.classList.remove(\"hfp-open\");\n speedBtn.setAttribute(\"aria-expanded\", \"false\");\n };\n document.addEventListener(\"click\", onDocClick);\n\n const handleScrubAt = (clientX: number) => {\n const rect = scrubber.getBoundingClientRect();\n const fraction = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));\n callbacks.onSeek(fraction);\n };\n\n let scrubbing = false;\n\n scrubber.addEventListener(\"mousedown\", (e) => {\n e.stopPropagation();\n scrubbing = true;\n handleScrubAt(e.clientX);\n });\n const onMouseMove = (e: MouseEvent) => {\n if (scrubbing) handleScrubAt(e.clientX);\n };\n const onMouseUp = () => {\n scrubbing = false;\n };\n document.addEventListener(\"mousemove\", onMouseMove);\n document.addEventListener(\"mouseup\", onMouseUp);\n\n scrubber.addEventListener(\n \"touchstart\",\n (e) => {\n scrubbing = true;\n const touch = e.touches[0];\n if (touch) handleScrubAt(touch.clientX);\n },\n { passive: true },\n );\n const onTouchMove = (e: TouchEvent) => {\n if (scrubbing) {\n const touch = e.touches[0];\n if (touch) handleScrubAt(touch.clientX);\n }\n };\n const onTouchEnd = () => {\n scrubbing = false;\n };\n document.addEventListener(\"touchmove\", onTouchMove, { passive: true });\n document.addEventListener(\"touchend\", onTouchEnd);\n\n const startHideTimer = () => {\n if (hideTimeout) clearTimeout(hideTimeout);\n hideTimeout = setTimeout(() => {\n if (isPlaying) controls.classList.add(\"hfp-hidden\");\n }, 3000);\n };\n\n const host = parent instanceof ShadowRoot ? (parent.host as HTMLElement) : parent;\n host.addEventListener(\"mousemove\", () => {\n controls.classList.remove(\"hfp-hidden\");\n startHideTimer();\n });\n host.addEventListener(\"mouseleave\", () => {\n if (isPlaying) controls.classList.add(\"hfp-hidden\");\n });\n\n return {\n updateTime(current: number, duration: number) {\n const pct = duration > 0 ? (current / duration) * 100 : 0;\n progress.style.width = `${pct}%`;\n time.textContent = `${formatTime(current)} / ${formatTime(duration)}`;\n },\n updatePlaying(playing: boolean) {\n isPlaying = playing;\n playBtn.innerHTML = playing ? PAUSE_ICON : PLAY_ICON;\n playBtn.setAttribute(\"aria-label\", playing ? \"Pause\" : \"Play\");\n if (playing) startHideTimer();\n else controls.classList.remove(\"hfp-hidden\");\n },\n updateSpeed(speed: number) {\n const idx = presets.indexOf(speed);\n if (idx !== -1) speedIndex = idx;\n speedBtn.textContent = formatSpeed(speed);\n setActiveOption(speed);\n },\n show() {\n controls.style.display = \"\";\n },\n hide() {\n controls.style.display = \"none\";\n },\n destroy() {\n document.removeEventListener(\"mousemove\", onMouseMove);\n document.removeEventListener(\"mouseup\", onMouseUp);\n document.removeEventListener(\"touchmove\", onTouchMove);\n document.removeEventListener(\"touchend\", onTouchEnd);\n document.removeEventListener(\"click\", onDocClick);\n if (hideTimeout) clearTimeout(hideTimeout);\n },\n };\n}\n","import { createControls, SPEED_PRESETS, type ControlsCallbacks } from \"./controls.js\";\nimport { PLAYER_STYLES } from \"./styles.js\";\n\nconst DEFAULT_FPS = 30;\nconst RUNTIME_CDN_URL =\n \"https://cdn.jsdelivr.net/npm/@hyperframes/core/dist/hyperframe.runtime.iife.js\";\n\nclass HyperframesPlayer extends HTMLElement {\n static get observedAttributes() {\n return [\"src\", \"width\", \"height\", \"controls\", \"muted\", \"poster\", \"playback-rate\", \"audio-src\"];\n }\n\n private shadow: ShadowRoot;\n private container: HTMLDivElement;\n private iframe: HTMLIFrameElement;\n private posterEl: HTMLImageElement | null = null;\n private controlsApi: ReturnType<typeof createControls> | null = null;\n private resizeObserver: ResizeObserver;\n\n private _ready = false;\n private _duration = 0;\n private _currentTime = 0;\n private _paused = true;\n private _compositionWidth = 1920;\n private _compositionHeight = 1080;\n private _probeInterval: ReturnType<typeof setInterval> | null = null;\n private _lastUpdateMs = 0;\n\n /**\n * Parent-frame media elements for mobile playback.\n *\n * Mobile browsers block media.play() inside iframes when the user gesture\n * happened in the parent frame — postMessage doesn't transfer user activation\n * (per the User Activation v2 spec). We extract ALL media sources from the\n * iframe's timed elements (audio/video with data-start), play them in the\n * parent frame (where the gesture lives), and disable the iframe copies.\n */\n private _parentMedia: Array<{\n el: HTMLMediaElement;\n start: number;\n duration: number;\n }> = [];\n\n constructor() {\n super();\n this.shadow = this.attachShadow({ mode: \"open\" });\n\n const style = document.createElement(\"style\");\n style.textContent = PLAYER_STYLES;\n this.shadow.appendChild(style);\n\n this.container = document.createElement(\"div\");\n this.container.className = \"hfp-container\";\n\n this.iframe = document.createElement(\"iframe\");\n this.iframe.className = \"hfp-iframe\";\n this.iframe.sandbox.add(\"allow-scripts\", \"allow-same-origin\");\n this.iframe.allow = \"autoplay; fullscreen\";\n this.iframe.referrerPolicy = \"no-referrer\";\n this.iframe.title = \"HyperFrames Composition\";\n\n this.container.appendChild(this.iframe);\n this.shadow.appendChild(this.container);\n\n // Clicking the bare player surface toggles play/pause.\n // Ignore shadow-DOM control interactions so overlay clicks don't double-handle.\n this.addEventListener(\"click\", (event) => {\n if (this._isControlsClick(event)) return;\n if (this._paused) this.play();\n else this.pause();\n });\n\n this.resizeObserver = new ResizeObserver(() => this._updateScale());\n\n this._onMessage = this._onMessage.bind(this);\n this._onIframeLoad = this._onIframeLoad.bind(this);\n }\n\n connectedCallback() {\n this.resizeObserver.observe(this);\n window.addEventListener(\"message\", this._onMessage);\n this.iframe.addEventListener(\"load\", this._onIframeLoad);\n\n if (this.hasAttribute(\"controls\")) this._setupControls();\n if (this.hasAttribute(\"poster\")) this._setupPoster();\n if (this.hasAttribute(\"audio-src\"))\n this._setupParentAudioFromUrl(this.getAttribute(\"audio-src\")!);\n if (this.hasAttribute(\"src\")) this.iframe.src = this.getAttribute(\"src\")!;\n }\n\n disconnectedCallback() {\n this.resizeObserver.disconnect();\n window.removeEventListener(\"message\", this._onMessage);\n this.iframe.removeEventListener(\"load\", this._onIframeLoad);\n if (this._probeInterval) clearInterval(this._probeInterval);\n this.controlsApi?.destroy();\n for (const m of this._parentMedia) {\n m.el.pause();\n m.el.src = \"\";\n }\n this._parentMedia = [];\n }\n\n attributeChangedCallback(name: string, _old: string | null, val: string | null) {\n switch (name) {\n case \"src\":\n if (val) {\n this._ready = false;\n this.iframe.src = val;\n }\n break;\n case \"width\":\n this._compositionWidth = parseInt(val || \"1920\", 10);\n this._updateScale();\n break;\n case \"height\":\n this._compositionHeight = parseInt(val || \"1080\", 10);\n this._updateScale();\n break;\n case \"controls\":\n if (val !== null) this._setupControls();\n else {\n this.controlsApi?.destroy();\n this.controlsApi = null;\n }\n break;\n case \"poster\":\n this._setupPoster();\n break;\n case \"playback-rate\": {\n const rate = parseFloat(val || \"1\");\n for (const m of this._parentMedia) m.el.playbackRate = rate;\n this._sendControl(\"set-playback-rate\", { playbackRate: rate });\n this.controlsApi?.updateSpeed(rate);\n this.dispatchEvent(new Event(\"ratechange\"));\n break;\n }\n case \"muted\":\n for (const m of this._parentMedia) m.el.muted = val !== null;\n this._sendControl(\"set-muted\", { muted: val !== null });\n break;\n case \"audio-src\":\n if (val) this._setupParentAudioFromUrl(val);\n break;\n }\n }\n\n // ── Public API ──\n\n /**\n * Access the inner `<iframe>` element rendering the composition.\n *\n * Use this when integrating the player with editors, recorders, or\n * timeline tools (e.g. `@hyperframes/studio`) that need to inspect\n * the composition's DOM or read its `__player` / `__timelines`\n * runtime objects.\n *\n * **Common pitfall:** the iframe lives inside the player's Shadow DOM.\n * Passing the `<hyperframes-player>` element itself to code that expects\n * an `<iframe>` will silently break — `.contentWindow` returns `null`.\n * Always extract `iframeElement` first:\n *\n * ```ts\n * // ❌ Wrong — element ref doesn't expose contentWindow\n * iframeRef.current = playerRef.current;\n *\n * // ✓ Right — bridge the actual iframe\n * iframeRef.current = playerRef.current.iframeElement;\n * ```\n */\n get iframeElement(): HTMLIFrameElement {\n return this.iframe;\n }\n\n play() {\n this._hidePoster();\n this._playParentMedia();\n this._sendControl(\"play\");\n this._paused = false;\n this.controlsApi?.updatePlaying(true);\n this.dispatchEvent(new Event(\"play\"));\n }\n\n pause() {\n this._pauseParentMedia();\n this._sendControl(\"pause\");\n this._paused = true;\n this.controlsApi?.updatePlaying(false);\n this.dispatchEvent(new Event(\"pause\"));\n }\n\n seek(timeInSeconds: number) {\n const frame = Math.round(timeInSeconds * DEFAULT_FPS);\n this._sendControl(\"seek\", { frame });\n this._currentTime = timeInSeconds;\n\n // Sync parent media positions (accounting for each element's start offset)\n for (const m of this._parentMedia) {\n const relTime = timeInSeconds - m.start;\n if (relTime >= 0 && relTime < m.duration) {\n m.el.currentTime = relTime;\n }\n }\n\n this._paused = true;\n this.controlsApi?.updatePlaying(false);\n this.controlsApi?.updateTime(this._currentTime, this._duration);\n }\n\n get currentTime() {\n return this._currentTime;\n }\n set currentTime(t: number) {\n this.seek(t);\n }\n\n get duration() {\n return this._duration;\n }\n get paused() {\n return this._paused;\n }\n get ready() {\n return this._ready;\n }\n\n get playbackRate() {\n return parseFloat(this.getAttribute(\"playback-rate\") || \"1\");\n }\n set playbackRate(r: number) {\n this.setAttribute(\"playback-rate\", String(r));\n }\n\n get muted() {\n return this.hasAttribute(\"muted\");\n }\n set muted(m: boolean) {\n if (m) this.setAttribute(\"muted\", \"\");\n else this.removeAttribute(\"muted\");\n }\n\n get loop() {\n return this.hasAttribute(\"loop\");\n }\n set loop(l: boolean) {\n if (l) this.setAttribute(\"loop\", \"\");\n else this.removeAttribute(\"loop\");\n }\n\n // ── Private ──\n\n private _sendControl(action: string, extra: Record<string, unknown> = {}) {\n try {\n this.iframe.contentWindow?.postMessage(\n { source: \"hf-parent\", type: \"control\", action, ...extra },\n \"*\",\n );\n } catch {\n /* cross-origin */\n }\n }\n\n private _isControlsClick(event: Event) {\n return event\n .composedPath()\n .some((target) => target instanceof HTMLElement && target.classList.contains(\"hfp-controls\"));\n }\n\n private _onMessage(e: MessageEvent) {\n if (e.source !== this.iframe.contentWindow) return;\n const data = e.data;\n if (!data || data.source !== \"hf-preview\") return;\n\n if (data.type === \"state\") {\n this._currentTime = (data.frame ?? 0) / DEFAULT_FPS;\n const wasPlaying = !this._paused;\n this._paused = !data.isPlaying;\n\n // Sync parent media on runtime play/pause transitions (e.g. browser\n // throttling, visibility change, or scrubber interaction in the iframe).\n if (wasPlaying && this._paused) {\n this._pauseParentMedia();\n } else if (!wasPlaying && !this._paused) {\n this._playParentMedia();\n }\n\n // Throttle UI updates and event dispatch to ~10fps to avoid excessive re-renders\n const now = performance.now();\n if (now - this._lastUpdateMs > 100 || this._paused !== wasPlaying) {\n this._lastUpdateMs = now;\n this.controlsApi?.updateTime(this._currentTime, this._duration);\n this.controlsApi?.updatePlaying(!this._paused);\n this.dispatchEvent(\n new CustomEvent(\"timeupdate\", { detail: { currentTime: this._currentTime } }),\n );\n }\n\n if (this._currentTime >= this._duration && !this._paused) {\n this._pauseParentMedia();\n if (this.loop) {\n this.seek(0);\n this.play();\n } else {\n this._paused = true;\n this.controlsApi?.updatePlaying(false);\n this.dispatchEvent(new Event(\"ended\"));\n }\n }\n }\n\n if (data.type === \"timeline\" && data.durationInFrames > 0) {\n // Ignore Infinity duration from runtime (caused by loop-inflated timelines without data-duration)\n // The player already has duration from the initial probe, so keep that.\n if (Number.isFinite(data.durationInFrames)) {\n this._duration = data.durationInFrames / DEFAULT_FPS;\n this.controlsApi?.updateTime(this._currentTime, this._duration);\n }\n }\n\n if (data.type === \"stage-size\" && data.width > 0 && data.height > 0) {\n this._compositionWidth = data.width;\n this._compositionHeight = data.height;\n this._updateScale();\n }\n }\n\n private _runtimeInjected = false;\n\n private _onIframeLoad() {\n let attempts = 0;\n this._runtimeInjected = false;\n if (this._probeInterval) clearInterval(this._probeInterval);\n\n this._probeInterval = setInterval(() => {\n attempts++;\n try {\n const win = this.iframe.contentWindow as Window & {\n __player?: { getDuration: () => number };\n __timelines?: Record<string, { duration: () => number }>;\n __hf?: unknown;\n };\n if (!win) return;\n\n // Check if the runtime bridge is active (__hf or __player from the runtime)\n const hasRuntime = !!(win.__hf || win.__player);\n const hasTimelines = !!(win.__timelines && Object.keys(win.__timelines).length > 0);\n\n // Auto-inject runtime if GSAP timelines exist but no runtime bridge\n if (!hasRuntime && hasTimelines && !this._runtimeInjected && attempts >= 5) {\n this._injectRuntime();\n return; // Wait for runtime to load and initialize\n }\n\n // Runtime was injected but hasn't loaded yet — keep waiting\n if (this._runtimeInjected && !hasRuntime) {\n return;\n }\n\n const getAdapter = () => {\n if (win.__player && typeof win.__player.getDuration === \"function\") return win.__player;\n if (win.__timelines) {\n const keys = Object.keys(win.__timelines);\n if (keys.length > 0) {\n // Resolve the root composition id from the DOM — the outermost\n // `[data-composition-id]` element is the master. Bundled previews\n // register the root composition alongside sub-compositions, and\n // without this lookup Object.keys() order would make a\n // sub-composition's duration hijack the overall video length.\n const rootId = this.iframe.contentDocument\n ?.querySelector(\"[data-composition-id]\")\n ?.getAttribute(\"data-composition-id\");\n const key = rootId && rootId in win.__timelines ? rootId : keys[keys.length - 1];\n const tl = win.__timelines[key];\n return { getDuration: () => tl.duration() };\n }\n }\n return null;\n };\n\n const adapter = getAdapter();\n if (adapter && adapter.getDuration() > 0) {\n clearInterval(this._probeInterval!);\n this._duration = adapter.getDuration();\n this._ready = true;\n this.controlsApi?.updateTime(0, this._duration);\n this.dispatchEvent(new CustomEvent(\"ready\", { detail: { duration: this._duration } }));\n\n // Auto-detect dimensions from composition\n const doc = this.iframe.contentDocument;\n const root = doc?.querySelector(\"[data-composition-id]\");\n if (root) {\n const w = parseInt(root.getAttribute(\"data-width\") || \"0\", 10);\n const h = parseInt(root.getAttribute(\"data-height\") || \"0\", 10);\n if (w > 0 && h > 0) {\n this._compositionWidth = w;\n this._compositionHeight = h;\n this._updateScale();\n }\n }\n\n this._setupParentMedia();\n\n if (this.hasAttribute(\"autoplay\")) {\n this.play();\n }\n return;\n }\n } catch {\n /* cross-origin */\n }\n\n if (attempts >= 40) {\n clearInterval(this._probeInterval!);\n this.dispatchEvent(\n new CustomEvent(\"error\", {\n detail: { message: \"Composition timeline not found after 8s\" },\n }),\n );\n }\n }, 200);\n }\n\n /** Inject the HyperFrames runtime into the iframe if not already present. */\n private _injectRuntime() {\n this._runtimeInjected = true;\n try {\n const doc = this.iframe.contentDocument;\n if (!doc) return;\n const script = doc.createElement(\"script\");\n script.src = RUNTIME_CDN_URL;\n script.onload = () => {\n // Runtime loaded — the probe interval will pick up __hf on next tick\n };\n script.onerror = () => {\n // CDN failed — the probe will continue and eventually timeout\n };\n (doc.head || doc.documentElement).appendChild(script);\n } catch {\n /* cross-origin — can't inject */\n }\n }\n\n private _updateScale() {\n const rect = this.getBoundingClientRect();\n if (rect.width === 0 || rect.height === 0) return;\n const scale = Math.min(\n rect.width / this._compositionWidth,\n rect.height / this._compositionHeight,\n );\n this.iframe.style.width = `${this._compositionWidth}px`;\n this.iframe.style.height = `${this._compositionHeight}px`;\n this.iframe.style.transform = `translate(-50%, -50%) scale(${scale})`;\n }\n\n private _setupControls() {\n if (this.controlsApi) return;\n const callbacks: ControlsCallbacks = {\n onPlay: () => this.play(),\n onPause: () => this.pause(),\n onSeek: (fraction) => this.seek(fraction * this._duration),\n onSpeedChange: (speed) => {\n this.playbackRate = speed;\n },\n };\n const presetsAttr = this.getAttribute(\"speed-presets\");\n const speedPresets = presetsAttr\n ? presetsAttr\n .split(\",\")\n .map(Number)\n .filter((n) => !isNaN(n) && n > 0)\n : undefined;\n this.controlsApi = createControls(this.shadow, callbacks, { speedPresets });\n }\n\n private _setupPoster() {\n const url = this.getAttribute(\"poster\");\n if (!url) {\n this.posterEl?.remove();\n this.posterEl = null;\n return;\n }\n if (!this.posterEl) {\n this.posterEl = document.createElement(\"img\");\n this.posterEl.className = \"hfp-poster\";\n this.shadow.appendChild(this.posterEl);\n }\n this.posterEl.src = url;\n }\n\n private _playParentMedia() {\n for (const m of this._parentMedia) {\n if (m.el.src) m.el.play().catch(() => {});\n }\n }\n\n private _pauseParentMedia() {\n for (const m of this._parentMedia) m.el.pause();\n }\n\n /** Create a parent-frame media element, configure it, and start preloading. */\n private _createParentMedia(src: string, tag: \"audio\" | \"video\", start: number, duration: number) {\n // Deduplicate — browsers normalize URLs so we compare on the element after assignment\n if (this._parentMedia.some((m) => m.el.src === src)) return;\n\n const el = tag === \"video\" ? document.createElement(\"video\") : new Audio();\n el.preload = \"auto\";\n el.src = src;\n el.load();\n el.muted = this.muted;\n if (this.playbackRate !== 1) el.playbackRate = this.playbackRate;\n\n this._parentMedia.push({ el, start, duration });\n }\n\n /**\n * Set up a single parent-frame audio from an explicit URL (via `audio-src`).\n * Convenience for the common single-narration case — starts preloading\n * immediately without waiting for the iframe to load.\n */\n private _setupParentAudioFromUrl(audioSrc: string) {\n this._createParentMedia(audioSrc, \"audio\", 0, Infinity);\n }\n\n /**\n * Extract ALL timed media (audio/video with data-start) from the iframe's\n * DOM and create parent-frame copies. Disables the iframe originals so the\n * runtime doesn't try to play them (which would fail on mobile and cause\n * double playback on desktop).\n *\n * If `audio-src` was already set, this just disables the iframe media.\n */\n private _setupParentMedia() {\n try {\n const doc = this.iframe.contentDocument;\n if (!doc) return;\n\n // Find all timed media — matches the runtime's media.ts selector\n const mediaEls = doc.querySelectorAll<HTMLMediaElement>(\n \"audio[data-start], video[data-start]\",\n );\n\n for (const iframeEl of mediaEls) {\n const src = iframeEl.getAttribute(\"src\") || iframeEl.querySelector(\"source\")?.src;\n if (!src) continue;\n\n const start = parseFloat(iframeEl.getAttribute(\"data-start\") || \"0\");\n const duration = parseFloat(iframeEl.getAttribute(\"data-duration\") || \"Infinity\");\n const tag = iframeEl.tagName === \"VIDEO\" ? (\"video\" as const) : (\"audio\" as const);\n\n this._createParentMedia(src, tag, start, duration);\n\n // Disable the iframe element so the runtime ignores it\n iframeEl.removeAttribute(\"src\");\n iframeEl.removeAttribute(\"data-start\");\n iframeEl.removeAttribute(\"data-duration\");\n iframeEl.querySelectorAll(\"source\").forEach((s) => s.remove());\n }\n } catch {\n // Cross-origin iframe — can't access DOM, fall back to iframe media\n }\n }\n\n private _hidePoster() {\n this.posterEl?.remove();\n this.posterEl = null;\n }\n}\n\nif (!customElements.get(\"hyperframes-player\")) {\n customElements.define(\"hyperframes-player\", HyperframesPlayer);\n}\n\nexport { HyperframesPlayer };\nexport { formatTime, formatSpeed, SPEED_PRESETS } from \"./controls.js\";\nexport type { ControlsCallbacks, ControlsOptions } from \"./controls.js\";\n"],"mappings":"AAAO,IAAMA,EAA0B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAuM1BC,EAAY,8GACZC,EAAa,gKC9LnB,IAAMC,EAAgB,CAAC,IAAM,GAAK,EAAG,IAAK,EAAG,CAAC,EAO9C,SAASC,EAAYC,EAAuB,CACjD,OAAO,OAAO,UAAUA,CAAK,EAAI,GAAGA,CAAK,IAAM,GAAGA,CAAK,GACzD,CAEO,SAASC,EAAWC,EAAyB,CAElD,GAAI,CAAC,OAAO,SAASA,CAAO,GAAKA,EAAU,EACzC,MAAO,OAET,IAAMC,EAAI,KAAK,MAAMD,CAAO,EACtBE,EAAI,KAAK,MAAMD,EAAI,EAAE,EACrBE,EAAMF,EAAI,GAChB,MAAO,GAAGC,CAAC,IAAIC,EAAI,SAAS,EAAE,SAAS,EAAG,GAAG,CAAC,EAChD,CAEO,SAASC,EACdC,EACAC,EACAC,EAA2B,CAAC,EAQ5B,CACA,IAAMC,EAAUD,EAAQ,cAAgBX,EAElCa,EAAW,SAAS,cAAc,KAAK,EAC7CA,EAAS,UAAY,eAErBA,EAAS,iBAAiB,QAAUC,GAAM,CACxCA,EAAE,gBAAgB,CACpB,CAAC,EAED,IAAMC,EAAU,SAAS,cAAc,QAAQ,EAC/CA,EAAQ,UAAY,eACpBA,EAAQ,KAAO,SACfA,EAAQ,UAAYC,EACpBD,EAAQ,aAAa,aAAc,MAAM,EAEzC,IAAME,EAAW,SAAS,cAAc,KAAK,EAC7CA,EAAS,UAAY,eACrB,IAAMC,EAAW,SAAS,cAAc,KAAK,EAC7CA,EAAS,UAAY,eACrBA,EAAS,MAAM,MAAQ,KACvBD,EAAS,YAAYC,CAAQ,EAE7B,IAAMC,EAAO,SAAS,cAAc,MAAM,EAC1CA,EAAK,UAAY,WACjBA,EAAK,YAAc,cAEnB,IAAMC,EAAY,SAAS,cAAc,KAAK,EAC9CA,EAAU,UAAY,iBAEtB,IAAMC,EAAW,SAAS,cAAc,QAAQ,EAChDA,EAAS,UAAY,gBACrBA,EAAS,KAAO,SAChBA,EAAS,YAAc,KACvBA,EAAS,aAAa,aAAc,gBAAgB,EAEpD,IAAMC,EAAY,SAAS,cAAc,KAAK,EAC9CA,EAAU,UAAY,iBACtBA,EAAU,aAAa,OAAQ,MAAM,EACrC,QAAWC,KAAUX,EAAS,CAC5B,IAAMY,EAAO,SAAS,cAAc,QAAQ,EAC5CA,EAAK,UAAY,mBACjBA,EAAK,KAAO,SACZA,EAAK,aAAa,OAAQ,UAAU,EACpCA,EAAK,QAAQ,MAAQ,OAAOD,CAAM,EAClCC,EAAK,YAAcvB,EAAYsB,CAAM,EACjCA,IAAW,GAAGC,EAAK,UAAU,IAAI,YAAY,EACjDF,EAAU,YAAYE,CAAI,CAC5B,CAEAJ,EAAU,YAAYE,CAAS,EAC/BF,EAAU,YAAYC,CAAQ,EAE9BR,EAAS,YAAYE,CAAO,EAC5BF,EAAS,YAAYI,CAAQ,EAC7BJ,EAAS,YAAYM,CAAI,EACzBN,EAAS,YAAYO,CAAS,EAC9BX,EAAO,YAAYI,CAAQ,EAE3B,IAAIY,EAAY,GACZC,EAAoD,KACpDC,EAAaf,EAAQ,QAAQ,CAAC,EAC9Be,IAAe,KAAIA,EAAa,GAEpCZ,EAAQ,iBAAiB,QAAUD,GAAM,CACvCA,EAAE,gBAAgB,EACdW,EAAWf,EAAU,QAAQ,EAC5BA,EAAU,OAAO,CACxB,CAAC,EAED,IAAMkB,EAAmB1B,GAAkB,CACzC,QAAW2B,KAAOP,EAAU,iBAAiB,mBAAmB,EAC9DO,EAAI,UAAU,OAAO,aAAeA,EAAoB,QAAQ,QAAU,OAAO3B,CAAK,CAAC,CAE3F,EAEAmB,EAAS,iBAAiB,QAAUP,GAAM,CACxCA,EAAE,gBAAgB,EAClB,IAAMgB,EAASR,EAAU,UAAU,OAAO,UAAU,EACpDD,EAAS,aAAa,gBAAiB,OAAOS,CAAM,CAAC,CACvD,CAAC,EAEDR,EAAU,iBAAiB,QAAUR,GAAM,CACzCA,EAAE,gBAAgB,EAClB,IAAMiB,EAAUjB,EAAE,OAAuB,QAAQ,mBAAmB,EACpE,GAAI,CAACiB,EAAQ,OACb,IAAMC,EAAW,WAAWD,EAAO,QAAQ,KAAM,EACjDJ,EAAaf,EAAQ,QAAQoB,CAAQ,EACrCX,EAAS,YAAcpB,EAAY+B,CAAQ,EAC3CJ,EAAgBI,CAAQ,EACxBV,EAAU,UAAU,OAAO,UAAU,EACrCD,EAAS,aAAa,gBAAiB,OAAO,EAC9CX,EAAU,cAAcsB,CAAQ,CAClC,CAAC,EAGD,IAAMC,EAAa,IAAM,CACvBX,EAAU,UAAU,OAAO,UAAU,EACrCD,EAAS,aAAa,gBAAiB,OAAO,CAChD,EACA,SAAS,iBAAiB,QAASY,CAAU,EAE7C,IAAMC,EAAiBC,GAAoB,CACzC,IAAMC,EAAOnB,EAAS,sBAAsB,EACtCoB,EAAW,KAAK,IAAI,EAAG,KAAK,IAAI,GAAIF,EAAUC,EAAK,MAAQA,EAAK,KAAK,CAAC,EAC5E1B,EAAU,OAAO2B,CAAQ,CAC3B,EAEIC,EAAY,GAEhBrB,EAAS,iBAAiB,YAAcH,GAAM,CAC5CA,EAAE,gBAAgB,EAClBwB,EAAY,GACZJ,EAAcpB,EAAE,OAAO,CACzB,CAAC,EACD,IAAMyB,EAAezB,GAAkB,CACjCwB,GAAWJ,EAAcpB,EAAE,OAAO,CACxC,EACM0B,EAAY,IAAM,CACtBF,EAAY,EACd,EACA,SAAS,iBAAiB,YAAaC,CAAW,EAClD,SAAS,iBAAiB,UAAWC,CAAS,EAE9CvB,EAAS,iBACP,aACCH,GAAM,CACLwB,EAAY,GACZ,IAAMG,EAAQ3B,EAAE,QAAQ,CAAC,EACrB2B,GAAOP,EAAcO,EAAM,OAAO,CACxC,EACA,CAAE,QAAS,EAAK,CAClB,EACA,IAAMC,EAAe5B,GAAkB,CACrC,GAAIwB,EAAW,CACb,IAAMG,EAAQ3B,EAAE,QAAQ,CAAC,EACrB2B,GAAOP,EAAcO,EAAM,OAAO,CACxC,CACF,EACME,EAAa,IAAM,CACvBL,EAAY,EACd,EACA,SAAS,iBAAiB,YAAaI,EAAa,CAAE,QAAS,EAAK,CAAC,EACrE,SAAS,iBAAiB,WAAYC,CAAU,EAEhD,IAAMC,EAAiB,IAAM,CACvBlB,GAAa,aAAaA,CAAW,EACzCA,EAAc,WAAW,IAAM,CACzBD,GAAWZ,EAAS,UAAU,IAAI,YAAY,CACpD,EAAG,GAAI,CACT,EAEMgC,EAAOpC,aAAkB,WAAcA,EAAO,KAAuBA,EAC3E,OAAAoC,EAAK,iBAAiB,YAAa,IAAM,CACvChC,EAAS,UAAU,OAAO,YAAY,EACtC+B,EAAe,CACjB,CAAC,EACDC,EAAK,iBAAiB,aAAc,IAAM,CACpCpB,GAAWZ,EAAS,UAAU,IAAI,YAAY,CACpD,CAAC,EAEM,CACL,WAAWiC,EAAiBC,EAAkB,CAC5C,IAAMC,EAAMD,EAAW,EAAKD,EAAUC,EAAY,IAAM,EACxD7B,EAAS,MAAM,MAAQ,GAAG8B,CAAG,IAC7B7B,EAAK,YAAc,GAAGhB,EAAW2C,CAAO,CAAC,MAAM3C,EAAW4C,CAAQ,CAAC,EACrE,EACA,cAAcE,EAAkB,CAC9BxB,EAAYwB,EACZlC,EAAQ,UAAYkC,EAAUC,EAAalC,EAC3CD,EAAQ,aAAa,aAAckC,EAAU,QAAU,MAAM,EACzDA,EAASL,EAAe,EACvB/B,EAAS,UAAU,OAAO,YAAY,CAC7C,EACA,YAAYX,EAAe,CACzB,IAAMiD,EAAMvC,EAAQ,QAAQV,CAAK,EAC7BiD,IAAQ,KAAIxB,EAAawB,GAC7B9B,EAAS,YAAcpB,EAAYC,CAAK,EACxC0B,EAAgB1B,CAAK,CACvB,EACA,MAAO,CACLW,EAAS,MAAM,QAAU,EAC3B,EACA,MAAO,CACLA,EAAS,MAAM,QAAU,MAC3B,EACA,SAAU,CACR,SAAS,oBAAoB,YAAa0B,CAAW,EACrD,SAAS,oBAAoB,UAAWC,CAAS,EACjD,SAAS,oBAAoB,YAAaE,CAAW,EACrD,SAAS,oBAAoB,WAAYC,CAAU,EACnD,SAAS,oBAAoB,QAASV,CAAU,EAC5CP,GAAa,aAAaA,CAAW,CAC3C,CACF,CACF,CC3OA,IAAM0B,EAAc,GACdC,EACJ,iFAEIC,EAAN,cAAgC,WAAY,CAC1C,WAAW,oBAAqB,CAC9B,MAAO,CAAC,MAAO,QAAS,SAAU,WAAY,QAAS,SAAU,gBAAiB,WAAW,CAC/F,CAEQ,OACA,UACA,OACA,SAAoC,KACpC,YAAwD,KACxD,eAEA,OAAS,GACT,UAAY,EACZ,aAAe,EACf,QAAU,GACV,kBAAoB,KACpB,mBAAqB,KACrB,eAAwD,KACxD,cAAgB,EAWhB,aAIH,CAAC,EAEN,aAAc,CACZ,MAAM,EACN,KAAK,OAAS,KAAK,aAAa,CAAE,KAAM,MAAO,CAAC,EAEhD,IAAMC,EAAQ,SAAS,cAAc,OAAO,EAC5CA,EAAM,YAAcC,EACpB,KAAK,OAAO,YAAYD,CAAK,EAE7B,KAAK,UAAY,SAAS,cAAc,KAAK,EAC7C,KAAK,UAAU,UAAY,gBAE3B,KAAK,OAAS,SAAS,cAAc,QAAQ,EAC7C,KAAK,OAAO,UAAY,aACxB,KAAK,OAAO,QAAQ,IAAI,gBAAiB,mBAAmB,EAC5D,KAAK,OAAO,MAAQ,uBACpB,KAAK,OAAO,eAAiB,cAC7B,KAAK,OAAO,MAAQ,0BAEpB,KAAK,UAAU,YAAY,KAAK,MAAM,EACtC,KAAK,OAAO,YAAY,KAAK,SAAS,EAItC,KAAK,iBAAiB,QAAUE,GAAU,CACpC,KAAK,iBAAiBA,CAAK,IAC3B,KAAK,QAAS,KAAK,KAAK,EACvB,KAAK,MAAM,EAClB,CAAC,EAED,KAAK,eAAiB,IAAI,eAAe,IAAM,KAAK,aAAa,CAAC,EAElE,KAAK,WAAa,KAAK,WAAW,KAAK,IAAI,EAC3C,KAAK,cAAgB,KAAK,cAAc,KAAK,IAAI,CACnD,CAEA,mBAAoB,CAClB,KAAK,eAAe,QAAQ,IAAI,EAChC,OAAO,iBAAiB,UAAW,KAAK,UAAU,EAClD,KAAK,OAAO,iBAAiB,OAAQ,KAAK,aAAa,EAEnD,KAAK,aAAa,UAAU,GAAG,KAAK,eAAe,EACnD,KAAK,aAAa,QAAQ,GAAG,KAAK,aAAa,EAC/C,KAAK,aAAa,WAAW,GAC/B,KAAK,yBAAyB,KAAK,aAAa,WAAW,CAAE,EAC3D,KAAK,aAAa,KAAK,IAAG,KAAK,OAAO,IAAM,KAAK,aAAa,KAAK,EACzE,CAEA,sBAAuB,CACrB,KAAK,eAAe,WAAW,EAC/B,OAAO,oBAAoB,UAAW,KAAK,UAAU,EACrD,KAAK,OAAO,oBAAoB,OAAQ,KAAK,aAAa,EACtD,KAAK,gBAAgB,cAAc,KAAK,cAAc,EAC1D,KAAK,aAAa,QAAQ,EAC1B,QAAWC,KAAK,KAAK,aACnBA,EAAE,GAAG,MAAM,EACXA,EAAE,GAAG,IAAM,GAEb,KAAK,aAAe,CAAC,CACvB,CAEA,yBAAyBC,EAAcC,EAAqBC,EAAoB,CAC9E,OAAQF,EAAM,CACZ,IAAK,MACCE,IACF,KAAK,OAAS,GACd,KAAK,OAAO,IAAMA,GAEpB,MACF,IAAK,QACH,KAAK,kBAAoB,SAASA,GAAO,OAAQ,EAAE,EACnD,KAAK,aAAa,EAClB,MACF,IAAK,SACH,KAAK,mBAAqB,SAASA,GAAO,OAAQ,EAAE,EACpD,KAAK,aAAa,EAClB,MACF,IAAK,WACCA,IAAQ,KAAM,KAAK,eAAe,GAEpC,KAAK,aAAa,QAAQ,EAC1B,KAAK,YAAc,MAErB,MACF,IAAK,SACH,KAAK,aAAa,EAClB,MACF,IAAK,gBAAiB,CACpB,IAAMC,EAAO,WAAWD,GAAO,GAAG,EAClC,QAAWH,KAAK,KAAK,aAAcA,EAAE,GAAG,aAAeI,EACvD,KAAK,aAAa,oBAAqB,CAAE,aAAcA,CAAK,CAAC,EAC7D,KAAK,aAAa,YAAYA,CAAI,EAClC,KAAK,cAAc,IAAI,MAAM,YAAY,CAAC,EAC1C,KACF,CACA,IAAK,QACH,QAAWJ,KAAK,KAAK,aAAcA,EAAE,GAAG,MAAQG,IAAQ,KACxD,KAAK,aAAa,YAAa,CAAE,MAAOA,IAAQ,IAAK,CAAC,EACtD,MACF,IAAK,YACCA,GAAK,KAAK,yBAAyBA,CAAG,EAC1C,KACJ,CACF,CAyBA,IAAI,eAAmC,CACrC,OAAO,KAAK,MACd,CAEA,MAAO,CACL,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,aAAa,MAAM,EACxB,KAAK,QAAU,GACf,KAAK,aAAa,cAAc,EAAI,EACpC,KAAK,cAAc,IAAI,MAAM,MAAM,CAAC,CACtC,CAEA,OAAQ,CACN,KAAK,kBAAkB,EACvB,KAAK,aAAa,OAAO,EACzB,KAAK,QAAU,GACf,KAAK,aAAa,cAAc,EAAK,EACrC,KAAK,cAAc,IAAI,MAAM,OAAO,CAAC,CACvC,CAEA,KAAKE,EAAuB,CAC1B,IAAMC,EAAQ,KAAK,MAAMD,EAAgBX,CAAW,EACpD,KAAK,aAAa,OAAQ,CAAE,MAAAY,CAAM,CAAC,EACnC,KAAK,aAAeD,EAGpB,QAAWL,KAAK,KAAK,aAAc,CACjC,IAAMO,EAAUF,EAAgBL,EAAE,MAC9BO,GAAW,GAAKA,EAAUP,EAAE,WAC9BA,EAAE,GAAG,YAAcO,EAEvB,CAEA,KAAK,QAAU,GACf,KAAK,aAAa,cAAc,EAAK,EACrC,KAAK,aAAa,WAAW,KAAK,aAAc,KAAK,SAAS,CAChE,CAEA,IAAI,aAAc,CAChB,OAAO,KAAK,YACd,CACA,IAAI,YAAYC,EAAW,CACzB,KAAK,KAAKA,CAAC,CACb,CAEA,IAAI,UAAW,CACb,OAAO,KAAK,SACd,CACA,IAAI,QAAS,CACX,OAAO,KAAK,OACd,CACA,IAAI,OAAQ,CACV,OAAO,KAAK,MACd,CAEA,IAAI,cAAe,CACjB,OAAO,WAAW,KAAK,aAAa,eAAe,GAAK,GAAG,CAC7D,CACA,IAAI,aAAaC,EAAW,CAC1B,KAAK,aAAa,gBAAiB,OAAOA,CAAC,CAAC,CAC9C,CAEA,IAAI,OAAQ,CACV,OAAO,KAAK,aAAa,OAAO,CAClC,CACA,IAAI,MAAMT,EAAY,CAChBA,EAAG,KAAK,aAAa,QAAS,EAAE,EAC/B,KAAK,gBAAgB,OAAO,CACnC,CAEA,IAAI,MAAO,CACT,OAAO,KAAK,aAAa,MAAM,CACjC,CACA,IAAI,KAAKU,EAAY,CACfA,EAAG,KAAK,aAAa,OAAQ,EAAE,EAC9B,KAAK,gBAAgB,MAAM,CAClC,CAIQ,aAAaC,EAAgBC,EAAiC,CAAC,EAAG,CACxE,GAAI,CACF,KAAK,OAAO,eAAe,YACzB,CAAE,OAAQ,YAAa,KAAM,UAAW,OAAAD,EAAQ,GAAGC,CAAM,EACzD,GACF,CACF,MAAQ,CAER,CACF,CAEQ,iBAAiBb,EAAc,CACrC,OAAOA,EACJ,aAAa,EACb,KAAMc,GAAWA,aAAkB,aAAeA,EAAO,UAAU,SAAS,cAAc,CAAC,CAChG,CAEQ,WAAW,EAAiB,CAClC,GAAI,EAAE,SAAW,KAAK,OAAO,cAAe,OAC5C,IAAMC,EAAO,EAAE,KACf,GAAI,GAACA,GAAQA,EAAK,SAAW,cAE7B,IAAIA,EAAK,OAAS,QAAS,CACzB,KAAK,cAAgBA,EAAK,OAAS,GAAKpB,EACxC,IAAMqB,EAAa,CAAC,KAAK,QACzB,KAAK,QAAU,CAACD,EAAK,UAIjBC,GAAc,KAAK,QACrB,KAAK,kBAAkB,EACd,CAACA,GAAc,CAAC,KAAK,SAC9B,KAAK,iBAAiB,EAIxB,IAAMC,EAAM,YAAY,IAAI,GACxBA,EAAM,KAAK,cAAgB,KAAO,KAAK,UAAYD,KACrD,KAAK,cAAgBC,EACrB,KAAK,aAAa,WAAW,KAAK,aAAc,KAAK,SAAS,EAC9D,KAAK,aAAa,cAAc,CAAC,KAAK,OAAO,EAC7C,KAAK,cACH,IAAI,YAAY,aAAc,CAAE,OAAQ,CAAE,YAAa,KAAK,YAAa,CAAE,CAAC,CAC9E,GAGE,KAAK,cAAgB,KAAK,WAAa,CAAC,KAAK,UAC/C,KAAK,kBAAkB,EACnB,KAAK,MACP,KAAK,KAAK,CAAC,EACX,KAAK,KAAK,IAEV,KAAK,QAAU,GACf,KAAK,aAAa,cAAc,EAAK,EACrC,KAAK,cAAc,IAAI,MAAM,OAAO,CAAC,GAG3C,CAEIF,EAAK,OAAS,YAAcA,EAAK,iBAAmB,GAGlD,OAAO,SAASA,EAAK,gBAAgB,IACvC,KAAK,UAAYA,EAAK,iBAAmBpB,EACzC,KAAK,aAAa,WAAW,KAAK,aAAc,KAAK,SAAS,GAI9DoB,EAAK,OAAS,cAAgBA,EAAK,MAAQ,GAAKA,EAAK,OAAS,IAChE,KAAK,kBAAoBA,EAAK,MAC9B,KAAK,mBAAqBA,EAAK,OAC/B,KAAK,aAAa,GAEtB,CAEQ,iBAAmB,GAEnB,eAAgB,CACtB,IAAIG,EAAW,EACf,KAAK,iBAAmB,GACpB,KAAK,gBAAgB,cAAc,KAAK,cAAc,EAE1D,KAAK,eAAiB,YAAY,IAAM,CACtCA,IACA,GAAI,CACF,IAAMC,EAAM,KAAK,OAAO,cAKxB,GAAI,CAACA,EAAK,OAGV,IAAMC,EAAa,CAAC,EAAED,EAAI,MAAQA,EAAI,UAChCE,EAAe,CAAC,EAAEF,EAAI,aAAe,OAAO,KAAKA,EAAI,WAAW,EAAE,OAAS,GAGjF,GAAI,CAACC,GAAcC,GAAgB,CAAC,KAAK,kBAAoBH,GAAY,EAAG,CAC1E,KAAK,eAAe,EACpB,MACF,CAGA,GAAI,KAAK,kBAAoB,CAACE,EAC5B,OAwBF,IAAME,GArBa,IAAM,CACvB,GAAIH,EAAI,UAAY,OAAOA,EAAI,SAAS,aAAgB,WAAY,OAAOA,EAAI,SAC/E,GAAIA,EAAI,YAAa,CACnB,IAAMI,EAAO,OAAO,KAAKJ,EAAI,WAAW,EACxC,GAAII,EAAK,OAAS,EAAG,CAMnB,IAAMC,EAAS,KAAK,OAAO,iBACvB,cAAc,uBAAuB,GACrC,aAAa,qBAAqB,EAChCC,EAAMD,GAAUA,KAAUL,EAAI,YAAcK,EAASD,EAAKA,EAAK,OAAS,CAAC,EACzEG,EAAKP,EAAI,YAAYM,CAAG,EAC9B,MAAO,CAAE,YAAa,IAAMC,EAAG,SAAS,CAAE,CAC5C,CACF,CACA,OAAO,IACT,GAE2B,EAC3B,GAAIJ,GAAWA,EAAQ,YAAY,EAAI,EAAG,CACxC,cAAc,KAAK,cAAe,EAClC,KAAK,UAAYA,EAAQ,YAAY,EACrC,KAAK,OAAS,GACd,KAAK,aAAa,WAAW,EAAG,KAAK,SAAS,EAC9C,KAAK,cAAc,IAAI,YAAY,QAAS,CAAE,OAAQ,CAAE,SAAU,KAAK,SAAU,CAAE,CAAC,CAAC,EAIrF,IAAMK,EADM,KAAK,OAAO,iBACN,cAAc,uBAAuB,EACvD,GAAIA,EAAM,CACR,IAAMC,EAAI,SAASD,EAAK,aAAa,YAAY,GAAK,IAAK,EAAE,EACvDE,EAAI,SAASF,EAAK,aAAa,aAAa,GAAK,IAAK,EAAE,EAC1DC,EAAI,GAAKC,EAAI,IACf,KAAK,kBAAoBD,EACzB,KAAK,mBAAqBC,EAC1B,KAAK,aAAa,EAEtB,CAEA,KAAK,kBAAkB,EAEnB,KAAK,aAAa,UAAU,GAC9B,KAAK,KAAK,EAEZ,MACF,CACF,MAAQ,CAER,CAEIX,GAAY,KACd,cAAc,KAAK,cAAe,EAClC,KAAK,cACH,IAAI,YAAY,QAAS,CACvB,OAAQ,CAAE,QAAS,yCAA0C,CAC/D,CAAC,CACH,EAEJ,EAAG,GAAG,CACR,CAGQ,gBAAiB,CACvB,KAAK,iBAAmB,GACxB,GAAI,CACF,IAAMY,EAAM,KAAK,OAAO,gBACxB,GAAI,CAACA,EAAK,OACV,IAAMC,EAASD,EAAI,cAAc,QAAQ,EACzCC,EAAO,IAAMnC,EACbmC,EAAO,OAAS,IAAM,CAEtB,EACAA,EAAO,QAAU,IAAM,CAEvB,GACCD,EAAI,MAAQA,EAAI,iBAAiB,YAAYC,CAAM,CACtD,MAAQ,CAER,CACF,CAEQ,cAAe,CACrB,IAAMC,EAAO,KAAK,sBAAsB,EACxC,GAAIA,EAAK,QAAU,GAAKA,EAAK,SAAW,EAAG,OAC3C,IAAMC,EAAQ,KAAK,IACjBD,EAAK,MAAQ,KAAK,kBAClBA,EAAK,OAAS,KAAK,kBACrB,EACA,KAAK,OAAO,MAAM,MAAQ,GAAG,KAAK,iBAAiB,KACnD,KAAK,OAAO,MAAM,OAAS,GAAG,KAAK,kBAAkB,KACrD,KAAK,OAAO,MAAM,UAAY,+BAA+BC,CAAK,GACpE,CAEQ,gBAAiB,CACvB,GAAI,KAAK,YAAa,OACtB,IAAMC,EAA+B,CACnC,OAAQ,IAAM,KAAK,KAAK,EACxB,QAAS,IAAM,KAAK,MAAM,EAC1B,OAASC,GAAa,KAAK,KAAKA,EAAW,KAAK,SAAS,EACzD,cAAgBC,GAAU,CACxB,KAAK,aAAeA,CACtB,CACF,EACMC,EAAc,KAAK,aAAa,eAAe,EAC/CC,EAAeD,EACjBA,EACG,MAAM,GAAG,EACT,IAAI,MAAM,EACV,OAAQE,GAAM,CAAC,MAAMA,CAAC,GAAKA,EAAI,CAAC,EACnC,OACJ,KAAK,YAAcC,EAAe,KAAK,OAAQN,EAAW,CAAE,aAAAI,CAAa,CAAC,CAC5E,CAEQ,cAAe,CACrB,IAAMG,EAAM,KAAK,aAAa,QAAQ,EACtC,GAAI,CAACA,EAAK,CACR,KAAK,UAAU,OAAO,EACtB,KAAK,SAAW,KAChB,MACF,CACK,KAAK,WACR,KAAK,SAAW,SAAS,cAAc,KAAK,EAC5C,KAAK,SAAS,UAAY,aAC1B,KAAK,OAAO,YAAY,KAAK,QAAQ,GAEvC,KAAK,SAAS,IAAMA,CACtB,CAEQ,kBAAmB,CACzB,QAAWxC,KAAK,KAAK,aACfA,EAAE,GAAG,KAAKA,EAAE,GAAG,KAAK,EAAE,MAAM,IAAM,CAAC,CAAC,CAE5C,CAEQ,mBAAoB,CAC1B,QAAWA,KAAK,KAAK,aAAcA,EAAE,GAAG,MAAM,CAChD,CAGQ,mBAAmByC,EAAaC,EAAwBC,EAAeC,EAAkB,CAE/F,GAAI,KAAK,aAAa,KAAM5C,GAAMA,EAAE,GAAG,MAAQyC,CAAG,EAAG,OAErD,IAAMI,EAAKH,IAAQ,QAAU,SAAS,cAAc,OAAO,EAAI,IAAI,MACnEG,EAAG,QAAU,OACbA,EAAG,IAAMJ,EACTI,EAAG,KAAK,EACRA,EAAG,MAAQ,KAAK,MACZ,KAAK,eAAiB,IAAGA,EAAG,aAAe,KAAK,cAEpD,KAAK,aAAa,KAAK,CAAE,GAAAA,EAAI,MAAAF,EAAO,SAAAC,CAAS,CAAC,CAChD,CAOQ,yBAAyBE,EAAkB,CACjD,KAAK,mBAAmBA,EAAU,QAAS,EAAG,GAAQ,CACxD,CAUQ,mBAAoB,CAC1B,GAAI,CACF,IAAMjB,EAAM,KAAK,OAAO,gBACxB,GAAI,CAACA,EAAK,OAGV,IAAMkB,EAAWlB,EAAI,iBACnB,sCACF,EAEA,QAAWmB,KAAYD,EAAU,CAC/B,IAAMN,EAAMO,EAAS,aAAa,KAAK,GAAKA,EAAS,cAAc,QAAQ,GAAG,IAC9E,GAAI,CAACP,EAAK,SAEV,IAAME,EAAQ,WAAWK,EAAS,aAAa,YAAY,GAAK,GAAG,EAC7DJ,EAAW,WAAWI,EAAS,aAAa,eAAe,GAAK,UAAU,EAC1EN,EAAMM,EAAS,UAAY,QAAW,QAAqB,QAEjE,KAAK,mBAAmBP,EAAKC,EAAKC,EAAOC,CAAQ,EAGjDI,EAAS,gBAAgB,KAAK,EAC9BA,EAAS,gBAAgB,YAAY,EACrCA,EAAS,gBAAgB,eAAe,EACxCA,EAAS,iBAAiB,QAAQ,EAAE,QAASC,GAAMA,EAAE,OAAO,CAAC,CAC/D,CACF,MAAQ,CAER,CACF,CAEQ,aAAc,CACpB,KAAK,UAAU,OAAO,EACtB,KAAK,SAAW,IAClB,CACF,EAEK,eAAe,IAAI,oBAAoB,GAC1C,eAAe,OAAO,qBAAsBrD,CAAiB","names":["PLAYER_STYLES","PLAY_ICON","PAUSE_ICON","SPEED_PRESETS","formatSpeed","speed","formatTime","seconds","s","m","sec","createControls","parent","callbacks","options","presets","controls","e","playBtn","PLAY_ICON","scrubber","progress","time","speedWrap","speedBtn","speedMenu","preset","item","isPlaying","hideTimeout","speedIndex","setActiveOption","opt","isOpen","target","newSpeed","onDocClick","handleScrubAt","clientX","rect","fraction","scrubbing","onMouseMove","onMouseUp","touch","onTouchMove","onTouchEnd","startHideTimer","host","current","duration","pct","playing","PAUSE_ICON","idx","DEFAULT_FPS","RUNTIME_CDN_URL","HyperframesPlayer","style","PLAYER_STYLES","event","m","name","_old","val","rate","timeInSeconds","frame","relTime","t","r","l","action","extra","target","data","wasPlaying","now","attempts","win","hasRuntime","hasTimelines","adapter","keys","rootId","key","tl","root","w","h","doc","script","rect","scale","callbacks","fraction","speed","presetsAttr","speedPresets","n","createControls","url","src","tag","start","duration","el","audioSrc","mediaEls","iframeEl","s"]}
|
|
1
|
+
{"version":3,"sources":["../src/styles.ts","../src/controls.ts","../src/hyperframes-player.ts"],"sourcesContent":["export const PLAYER_STYLES = /* css */ `\n :host {\n display: block;\n position: relative;\n overflow: hidden;\n background: #000;\n contain: layout style;\n }\n\n .hfp-container {\n position: absolute;\n inset: 0;\n overflow: hidden;\n pointer-events: none;\n }\n\n\n .hfp-iframe {\n position: absolute;\n top: 50%;\n left: 50%;\n border: none;\n pointer-events: none;\n }\n\n .hfp-poster {\n position: absolute;\n inset: 0;\n object-fit: contain;\n z-index: 1;\n pointer-events: none;\n }\n\n /* ── Theming via CSS custom properties ──\n *\n * Override from outside the shadow DOM:\n * hyperframes-player {\n * --hfp-controls-bg: linear-gradient(transparent, rgba(0,0,0,0.9));\n * --hfp-accent: #ff6b6b;\n * --hfp-font: \"Inter\", sans-serif;\n * }\n */\n\n .hfp-controls {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n display: flex;\n align-items: center;\n gap: var(--hfp-controls-gap, 12px);\n padding: var(--hfp-controls-padding, 8px 16px);\n background: var(--hfp-controls-bg, linear-gradient(transparent, rgba(0, 0, 0, 0.7)));\n color: var(--hfp-color, #fff);\n font-family: var(--hfp-font, system-ui, -apple-system, sans-serif);\n font-size: var(--hfp-font-size, 13px);\n z-index: 10;\n pointer-events: auto;\n opacity: 1;\n transition: opacity 0.3s ease;\n user-select: none;\n }\n\n .hfp-controls.hfp-hidden {\n opacity: 0;\n pointer-events: none;\n }\n\n .hfp-play-btn {\n background: none;\n border: none;\n color: var(--hfp-color, #fff);\n cursor: pointer;\n padding: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n flex-shrink: 0;\n z-index: 10;\n }\n\n .hfp-play-btn:hover {\n opacity: 0.8;\n }\n\n .hfp-play-btn svg,\n .hfp-play-btn svg * {\n pointer-events: none;\n }\n\n .hfp-scrubber {\n flex: 1;\n height: var(--hfp-scrubber-height, 4px);\n background: var(--hfp-scrubber-bg, rgba(255, 255, 255, 0.3));\n border-radius: var(--hfp-scrubber-radius, 2px);\n cursor: pointer;\n position: relative;\n }\n\n .hfp-scrubber:hover {\n height: var(--hfp-scrubber-height-hover, 6px);\n }\n\n .hfp-progress {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n background: var(--hfp-accent, #fff);\n border-radius: var(--hfp-scrubber-radius, 2px);\n pointer-events: none;\n }\n\n .hfp-time {\n flex-shrink: 0;\n font-variant-numeric: tabular-nums;\n opacity: 0.9;\n }\n\n .hfp-speed-wrap {\n position: relative;\n flex-shrink: 0;\n }\n\n .hfp-speed-btn {\n background: var(--hfp-speed-btn-bg, rgba(255, 255, 255, 0.15));\n border: none;\n border-radius: var(--hfp-speed-btn-radius, 4px);\n color: var(--hfp-color, #fff);\n cursor: pointer;\n font-family: var(--hfp-font, system-ui, -apple-system, sans-serif);\n font-size: 12px;\n font-variant-numeric: tabular-nums;\n font-weight: 600;\n padding: 4px 8px;\n min-width: 40px;\n text-align: center;\n transition: background 0.15s ease;\n }\n\n .hfp-speed-btn:hover {\n background: var(--hfp-speed-btn-bg-hover, rgba(255, 255, 255, 0.3));\n }\n\n .hfp-speed-menu {\n position: absolute;\n bottom: calc(100% + 8px);\n right: 0;\n background: var(--hfp-menu-bg, rgba(20, 20, 20, 0.95));\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n border: 1px solid var(--hfp-menu-border, rgba(255, 255, 255, 0.1));\n border-radius: var(--hfp-menu-radius, 8px);\n padding: 4px;\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 80px;\n opacity: 0;\n visibility: hidden;\n transform: translateY(4px);\n transition: opacity 0.15s ease, transform 0.15s ease, visibility 0.15s;\n box-shadow: var(--hfp-menu-shadow, 0 8px 24px rgba(0, 0, 0, 0.4));\n }\n\n .hfp-speed-menu.hfp-open {\n opacity: 1;\n visibility: visible;\n transform: translateY(0);\n }\n\n .hfp-speed-option {\n background: none;\n border: none;\n border-radius: 4px;\n color: var(--hfp-menu-color, rgba(255, 255, 255, 0.7));\n cursor: pointer;\n font-family: var(--hfp-font, system-ui, -apple-system, sans-serif);\n font-size: 13px;\n font-variant-numeric: tabular-nums;\n padding: 6px 12px;\n text-align: left;\n transition: background 0.1s ease, color 0.1s ease;\n white-space: nowrap;\n }\n\n .hfp-speed-option:hover {\n background: var(--hfp-menu-hover-bg, rgba(255, 255, 255, 0.1));\n color: var(--hfp-color, #fff);\n }\n\n .hfp-speed-option.hfp-active {\n color: var(--hfp-accent, #fff);\n font-weight: 600;\n }\n`;\n\nexport const PLAY_ICON = `<svg width=\"24\" height=\"24\" viewBox=\"0 0 18 18\" fill=\"currentColor\"><polygon points=\"4,2 16,9 4,16\"/></svg>`;\nexport const PAUSE_ICON = `<svg width=\"24\" height=\"24\" viewBox=\"0 0 18 18\" fill=\"currentColor\"><rect x=\"3\" y=\"2\" width=\"4\" height=\"14\"/><rect x=\"11\" y=\"2\" width=\"4\" height=\"14\"/></svg>`;\n","import { PLAY_ICON, PAUSE_ICON } from \"./styles.js\";\n\nexport interface ControlsCallbacks {\n onPlay: () => void;\n onPause: () => void;\n onSeek: (fraction: number) => void;\n onSpeedChange: (speed: number) => void;\n}\n\n/** Default logarithmic speed presets — each step roughly doubles/halves. */\nexport const SPEED_PRESETS = [0.25, 0.5, 1, 1.5, 2, 4] as const;\n\nexport interface ControlsOptions {\n /** Speed presets shown in the menu. Defaults to SPEED_PRESETS. */\n speedPresets?: readonly number[];\n}\n\nexport function formatSpeed(speed: number): string {\n return Number.isInteger(speed) ? `${speed}x` : `${speed}x`;\n}\n\nexport function formatTime(seconds: number): string {\n // Handle non-finite values gracefully\n if (!Number.isFinite(seconds) || seconds < 0) {\n return \"0:00\";\n }\n const s = Math.floor(seconds);\n const m = Math.floor(s / 60);\n const sec = s % 60;\n return `${m}:${sec.toString().padStart(2, \"0\")}`;\n}\n\nexport function createControls(\n parent: ShadowRoot | HTMLElement,\n callbacks: ControlsCallbacks,\n options: ControlsOptions = {},\n): {\n updateTime: (current: number, duration: number) => void;\n updatePlaying: (playing: boolean) => void;\n updateSpeed: (speed: number) => void;\n show: () => void;\n hide: () => void;\n destroy: () => void;\n} {\n const presets = options.speedPresets ?? SPEED_PRESETS;\n\n const controls = document.createElement(\"div\");\n controls.className = \"hfp-controls\";\n // Keep overlay interactions from falling through to the host-level click toggle.\n controls.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n });\n\n const playBtn = document.createElement(\"button\");\n playBtn.className = \"hfp-play-btn\";\n playBtn.type = \"button\";\n playBtn.innerHTML = PLAY_ICON;\n playBtn.setAttribute(\"aria-label\", \"Play\");\n\n const scrubber = document.createElement(\"div\");\n scrubber.className = \"hfp-scrubber\";\n const progress = document.createElement(\"div\");\n progress.className = \"hfp-progress\";\n progress.style.width = \"0%\";\n scrubber.appendChild(progress);\n\n const time = document.createElement(\"span\");\n time.className = \"hfp-time\";\n time.textContent = \"0:00 / 0:00\";\n\n const speedWrap = document.createElement(\"div\");\n speedWrap.className = \"hfp-speed-wrap\";\n\n const speedBtn = document.createElement(\"button\");\n speedBtn.className = \"hfp-speed-btn\";\n speedBtn.type = \"button\";\n speedBtn.textContent = \"1x\";\n speedBtn.setAttribute(\"aria-label\", \"Playback speed\");\n\n const speedMenu = document.createElement(\"div\");\n speedMenu.className = \"hfp-speed-menu\";\n speedMenu.setAttribute(\"role\", \"menu\");\n for (const preset of presets) {\n const item = document.createElement(\"button\");\n item.className = \"hfp-speed-option\";\n item.type = \"button\";\n item.setAttribute(\"role\", \"menuitem\");\n item.dataset.speed = String(preset);\n item.textContent = formatSpeed(preset);\n if (preset === 1) item.classList.add(\"hfp-active\");\n speedMenu.appendChild(item);\n }\n\n speedWrap.appendChild(speedMenu);\n speedWrap.appendChild(speedBtn);\n\n controls.appendChild(playBtn);\n controls.appendChild(scrubber);\n controls.appendChild(time);\n controls.appendChild(speedWrap);\n parent.appendChild(controls);\n\n let isPlaying = false;\n let hideTimeout: ReturnType<typeof setTimeout> | null = null;\n let speedIndex = presets.indexOf(1); // start at 1x\n if (speedIndex === -1) speedIndex = 0;\n\n playBtn.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n if (isPlaying) callbacks.onPause();\n else callbacks.onPlay();\n });\n\n const setActiveOption = (speed: number) => {\n for (const opt of speedMenu.querySelectorAll(\".hfp-speed-option\")) {\n opt.classList.toggle(\"hfp-active\", (opt as HTMLElement).dataset.speed === String(speed));\n }\n };\n\n speedBtn.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n const isOpen = speedMenu.classList.toggle(\"hfp-open\");\n speedBtn.setAttribute(\"aria-expanded\", String(isOpen));\n });\n\n speedMenu.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n const target = (e.target as HTMLElement).closest(\".hfp-speed-option\") as HTMLElement | null;\n if (!target) return;\n const newSpeed = parseFloat(target.dataset.speed!);\n speedIndex = presets.indexOf(newSpeed);\n speedBtn.textContent = formatSpeed(newSpeed);\n setActiveOption(newSpeed);\n speedMenu.classList.remove(\"hfp-open\");\n speedBtn.setAttribute(\"aria-expanded\", \"false\");\n callbacks.onSpeedChange(newSpeed);\n });\n\n // Close menu when clicking outside\n const onDocClick = () => {\n speedMenu.classList.remove(\"hfp-open\");\n speedBtn.setAttribute(\"aria-expanded\", \"false\");\n };\n document.addEventListener(\"click\", onDocClick);\n\n const handleScrubAt = (clientX: number) => {\n const rect = scrubber.getBoundingClientRect();\n const fraction = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));\n callbacks.onSeek(fraction);\n };\n\n let scrubbing = false;\n\n scrubber.addEventListener(\"mousedown\", (e) => {\n e.stopPropagation();\n scrubbing = true;\n handleScrubAt(e.clientX);\n });\n const onMouseMove = (e: MouseEvent) => {\n if (scrubbing) handleScrubAt(e.clientX);\n };\n const onMouseUp = () => {\n scrubbing = false;\n };\n document.addEventListener(\"mousemove\", onMouseMove);\n document.addEventListener(\"mouseup\", onMouseUp);\n\n scrubber.addEventListener(\n \"touchstart\",\n (e) => {\n scrubbing = true;\n const touch = e.touches[0];\n if (touch) handleScrubAt(touch.clientX);\n },\n { passive: true },\n );\n const onTouchMove = (e: TouchEvent) => {\n if (scrubbing) {\n const touch = e.touches[0];\n if (touch) handleScrubAt(touch.clientX);\n }\n };\n const onTouchEnd = () => {\n scrubbing = false;\n };\n document.addEventListener(\"touchmove\", onTouchMove, { passive: true });\n document.addEventListener(\"touchend\", onTouchEnd);\n\n const startHideTimer = () => {\n if (hideTimeout) clearTimeout(hideTimeout);\n hideTimeout = setTimeout(() => {\n if (isPlaying) controls.classList.add(\"hfp-hidden\");\n }, 3000);\n };\n\n const host = parent instanceof ShadowRoot ? (parent.host as HTMLElement) : parent;\n host.addEventListener(\"mousemove\", () => {\n controls.classList.remove(\"hfp-hidden\");\n startHideTimer();\n });\n host.addEventListener(\"mouseleave\", () => {\n if (isPlaying) controls.classList.add(\"hfp-hidden\");\n });\n\n return {\n updateTime(current: number, duration: number) {\n const pct = duration > 0 ? (current / duration) * 100 : 0;\n progress.style.width = `${pct}%`;\n time.textContent = `${formatTime(current)} / ${formatTime(duration)}`;\n },\n updatePlaying(playing: boolean) {\n isPlaying = playing;\n playBtn.innerHTML = playing ? PAUSE_ICON : PLAY_ICON;\n playBtn.setAttribute(\"aria-label\", playing ? \"Pause\" : \"Play\");\n if (playing) startHideTimer();\n else controls.classList.remove(\"hfp-hidden\");\n },\n updateSpeed(speed: number) {\n const idx = presets.indexOf(speed);\n if (idx !== -1) speedIndex = idx;\n speedBtn.textContent = formatSpeed(speed);\n setActiveOption(speed);\n },\n show() {\n controls.style.display = \"\";\n },\n hide() {\n controls.style.display = \"none\";\n },\n destroy() {\n document.removeEventListener(\"mousemove\", onMouseMove);\n document.removeEventListener(\"mouseup\", onMouseUp);\n document.removeEventListener(\"touchmove\", onTouchMove);\n document.removeEventListener(\"touchend\", onTouchEnd);\n document.removeEventListener(\"click\", onDocClick);\n if (hideTimeout) clearTimeout(hideTimeout);\n },\n };\n}\n","import { createControls, SPEED_PRESETS, type ControlsCallbacks } from \"./controls.js\";\nimport { PLAYER_STYLES } from \"./styles.js\";\n\nconst DEFAULT_FPS = 30;\nconst RUNTIME_CDN_URL =\n \"https://cdn.jsdelivr.net/npm/@hyperframes/core/dist/hyperframe.runtime.iife.js\";\n\nclass HyperframesPlayer extends HTMLElement {\n static get observedAttributes() {\n return [\"src\", \"width\", \"height\", \"controls\", \"muted\", \"poster\", \"playback-rate\", \"audio-src\"];\n }\n\n private shadow: ShadowRoot;\n private container: HTMLDivElement;\n private iframe: HTMLIFrameElement;\n private posterEl: HTMLImageElement | null = null;\n private controlsApi: ReturnType<typeof createControls> | null = null;\n private resizeObserver: ResizeObserver;\n\n private _ready = false;\n private _duration = 0;\n private _currentTime = 0;\n private _paused = true;\n private _compositionWidth = 1920;\n private _compositionHeight = 1080;\n private _probeInterval: ReturnType<typeof setInterval> | null = null;\n private _lastUpdateMs = 0;\n\n /**\n * Parent-frame media elements for mobile playback.\n *\n * Mobile browsers block media.play() inside iframes when the user gesture\n * happened in the parent frame — postMessage doesn't transfer user activation\n * (per the User Activation v2 spec). We extract ALL media sources from the\n * iframe's timed elements (audio/video with data-start), play them in the\n * parent frame (where the gesture lives), and disable the iframe copies.\n */\n private _parentMedia: Array<{\n el: HTMLMediaElement;\n start: number;\n duration: number;\n }> = [];\n\n constructor() {\n super();\n this.shadow = this.attachShadow({ mode: \"open\" });\n\n const style = document.createElement(\"style\");\n style.textContent = PLAYER_STYLES;\n this.shadow.appendChild(style);\n\n this.container = document.createElement(\"div\");\n this.container.className = \"hfp-container\";\n\n this.iframe = document.createElement(\"iframe\");\n this.iframe.className = \"hfp-iframe\";\n this.iframe.sandbox.add(\"allow-scripts\", \"allow-same-origin\");\n this.iframe.allow = \"autoplay; fullscreen\";\n this.iframe.referrerPolicy = \"no-referrer\";\n this.iframe.title = \"HyperFrames Composition\";\n\n this.container.appendChild(this.iframe);\n this.shadow.appendChild(this.container);\n\n // Clicking the bare player surface toggles play/pause.\n // Ignore shadow-DOM control interactions so overlay clicks don't double-handle.\n this.addEventListener(\"click\", (event) => {\n if (this._isControlsClick(event)) return;\n if (this._paused) this.play();\n else this.pause();\n });\n\n this.resizeObserver = new ResizeObserver(() => this._updateScale());\n\n this._onMessage = this._onMessage.bind(this);\n this._onIframeLoad = this._onIframeLoad.bind(this);\n }\n\n connectedCallback() {\n this.resizeObserver.observe(this);\n window.addEventListener(\"message\", this._onMessage);\n this.iframe.addEventListener(\"load\", this._onIframeLoad);\n\n if (this.hasAttribute(\"controls\")) this._setupControls();\n if (this.hasAttribute(\"poster\")) this._setupPoster();\n if (this.hasAttribute(\"audio-src\"))\n this._setupParentAudioFromUrl(this.getAttribute(\"audio-src\")!);\n if (this.hasAttribute(\"src\")) this.iframe.src = this.getAttribute(\"src\")!;\n }\n\n disconnectedCallback() {\n this.resizeObserver.disconnect();\n window.removeEventListener(\"message\", this._onMessage);\n this.iframe.removeEventListener(\"load\", this._onIframeLoad);\n if (this._probeInterval) clearInterval(this._probeInterval);\n this.controlsApi?.destroy();\n for (const m of this._parentMedia) {\n m.el.pause();\n m.el.src = \"\";\n }\n this._parentMedia = [];\n }\n\n attributeChangedCallback(name: string, _old: string | null, val: string | null) {\n switch (name) {\n case \"src\":\n if (val) {\n this._ready = false;\n this.iframe.src = val;\n }\n break;\n case \"width\":\n this._compositionWidth = parseInt(val || \"1920\", 10);\n this._updateScale();\n break;\n case \"height\":\n this._compositionHeight = parseInt(val || \"1080\", 10);\n this._updateScale();\n break;\n case \"controls\":\n if (val !== null) this._setupControls();\n else {\n this.controlsApi?.destroy();\n this.controlsApi = null;\n }\n break;\n case \"poster\":\n this._setupPoster();\n break;\n case \"playback-rate\": {\n const rate = parseFloat(val || \"1\");\n for (const m of this._parentMedia) m.el.playbackRate = rate;\n this._sendControl(\"set-playback-rate\", { playbackRate: rate });\n this.controlsApi?.updateSpeed(rate);\n this.dispatchEvent(new Event(\"ratechange\"));\n break;\n }\n case \"muted\":\n for (const m of this._parentMedia) m.el.muted = val !== null;\n this._sendControl(\"set-muted\", { muted: val !== null });\n break;\n case \"audio-src\":\n if (val) this._setupParentAudioFromUrl(val);\n break;\n }\n }\n\n // ── Public API ──\n\n /**\n * Access the inner `<iframe>` element rendering the composition.\n *\n * Use this when integrating the player with editors, recorders, or\n * timeline tools (e.g. `@hyperframes/studio`) that need to inspect\n * the composition's DOM or read its `__player` / `__timelines`\n * runtime objects.\n *\n * **Common pitfall:** the iframe lives inside the player's Shadow DOM.\n * Passing the `<hyperframes-player>` element itself to code that expects\n * an `<iframe>` will silently break — `.contentWindow` returns `null`.\n * Always extract `iframeElement` first:\n *\n * ```ts\n * // ❌ Wrong — element ref doesn't expose contentWindow\n * iframeRef.current = playerRef.current;\n *\n * // ✓ Right — bridge the actual iframe\n * iframeRef.current = playerRef.current.iframeElement;\n * ```\n */\n get iframeElement(): HTMLIFrameElement {\n return this.iframe;\n }\n\n play() {\n this._hidePoster();\n this._playParentMedia();\n this._sendControl(\"play\");\n this._paused = false;\n this.controlsApi?.updatePlaying(true);\n this.dispatchEvent(new Event(\"play\"));\n }\n\n pause() {\n this._pauseParentMedia();\n this._sendControl(\"pause\");\n this._paused = true;\n this.controlsApi?.updatePlaying(false);\n this.dispatchEvent(new Event(\"pause\"));\n }\n\n seek(timeInSeconds: number) {\n const frame = Math.round(timeInSeconds * DEFAULT_FPS);\n this._sendControl(\"seek\", { frame });\n this._currentTime = timeInSeconds;\n\n // Sync parent media positions (accounting for each element's start offset)\n for (const m of this._parentMedia) {\n const relTime = timeInSeconds - m.start;\n if (relTime >= 0 && relTime < m.duration) {\n m.el.currentTime = relTime;\n }\n }\n\n this._paused = true;\n this.controlsApi?.updatePlaying(false);\n this.controlsApi?.updateTime(this._currentTime, this._duration);\n }\n\n get currentTime() {\n return this._currentTime;\n }\n set currentTime(t: number) {\n this.seek(t);\n }\n\n get duration() {\n return this._duration;\n }\n get paused() {\n return this._paused;\n }\n get ready() {\n return this._ready;\n }\n\n get playbackRate() {\n return parseFloat(this.getAttribute(\"playback-rate\") || \"1\");\n }\n set playbackRate(r: number) {\n this.setAttribute(\"playback-rate\", String(r));\n }\n\n get muted() {\n return this.hasAttribute(\"muted\");\n }\n set muted(m: boolean) {\n if (m) this.setAttribute(\"muted\", \"\");\n else this.removeAttribute(\"muted\");\n }\n\n get loop() {\n return this.hasAttribute(\"loop\");\n }\n set loop(l: boolean) {\n if (l) this.setAttribute(\"loop\", \"\");\n else this.removeAttribute(\"loop\");\n }\n\n // ── Private ──\n\n private _sendControl(action: string, extra: Record<string, unknown> = {}) {\n try {\n this.iframe.contentWindow?.postMessage(\n { source: \"hf-parent\", type: \"control\", action, ...extra },\n \"*\",\n );\n } catch {\n /* cross-origin */\n }\n }\n\n private _isControlsClick(event: Event) {\n return event\n .composedPath()\n .some((target) => target instanceof HTMLElement && target.classList.contains(\"hfp-controls\"));\n }\n\n private _onMessage(e: MessageEvent) {\n if (e.source !== this.iframe.contentWindow) return;\n const data = e.data;\n if (!data || data.source !== \"hf-preview\") return;\n\n if (data.type === \"state\") {\n this._currentTime = (data.frame ?? 0) / DEFAULT_FPS;\n const wasPlaying = !this._paused;\n this._paused = !data.isPlaying;\n\n // Sync parent media on runtime play/pause transitions (e.g. browser\n // throttling, visibility change, or scrubber interaction in the iframe).\n if (wasPlaying && this._paused) {\n this._pauseParentMedia();\n } else if (!wasPlaying && !this._paused) {\n this._playParentMedia();\n }\n\n // Throttle UI updates and event dispatch to ~10fps to avoid excessive re-renders\n const now = performance.now();\n if (now - this._lastUpdateMs > 100 || this._paused !== wasPlaying) {\n this._lastUpdateMs = now;\n this.controlsApi?.updateTime(this._currentTime, this._duration);\n this.controlsApi?.updatePlaying(!this._paused);\n this.dispatchEvent(\n new CustomEvent(\"timeupdate\", { detail: { currentTime: this._currentTime } }),\n );\n }\n\n if (this._currentTime >= this._duration && !this._paused) {\n this._pauseParentMedia();\n if (this.loop) {\n this.seek(0);\n this.play();\n } else {\n this._paused = true;\n this.controlsApi?.updatePlaying(false);\n this.dispatchEvent(new Event(\"ended\"));\n }\n }\n }\n\n if (data.type === \"timeline\" && data.durationInFrames > 0) {\n // Ignore Infinity duration from runtime (caused by loop-inflated timelines without data-duration)\n // The player already has duration from the initial probe, so keep that.\n if (Number.isFinite(data.durationInFrames)) {\n this._duration = data.durationInFrames / DEFAULT_FPS;\n this.controlsApi?.updateTime(this._currentTime, this._duration);\n }\n }\n\n if (data.type === \"stage-size\" && data.width > 0 && data.height > 0) {\n this._compositionWidth = data.width;\n this._compositionHeight = data.height;\n this._updateScale();\n }\n }\n\n private _runtimeInjected = false;\n\n private _onIframeLoad() {\n let attempts = 0;\n this._runtimeInjected = false;\n if (this._probeInterval) clearInterval(this._probeInterval);\n\n this._probeInterval = setInterval(() => {\n attempts++;\n try {\n const win = this.iframe.contentWindow as Window & {\n __player?: { getDuration: () => number };\n __timelines?: Record<string, { duration: () => number }>;\n __hf?: unknown;\n };\n if (!win) return;\n\n // Check if the runtime bridge is active (__hf or __player from the runtime)\n const hasRuntime = !!(win.__hf || win.__player);\n const hasTimelines = !!(win.__timelines && Object.keys(win.__timelines).length > 0);\n\n // Auto-inject runtime if GSAP timelines exist but no runtime bridge\n if (!hasRuntime && hasTimelines && !this._runtimeInjected && attempts >= 5) {\n this._injectRuntime();\n return; // Wait for runtime to load and initialize\n }\n\n // Runtime was injected but hasn't loaded yet — keep waiting\n if (this._runtimeInjected && !hasRuntime) {\n return;\n }\n\n const getAdapter = () => {\n if (win.__player && typeof win.__player.getDuration === \"function\") return win.__player;\n if (win.__timelines) {\n const keys = Object.keys(win.__timelines);\n if (keys.length > 0) {\n // Resolve the root composition id from the DOM — the outermost\n // `[data-composition-id]` element is the master. Bundled previews\n // register the root composition alongside sub-compositions, and\n // without this lookup Object.keys() order would make a\n // sub-composition's duration hijack the overall video length.\n const rootId = this.iframe.contentDocument\n ?.querySelector(\"[data-composition-id]\")\n ?.getAttribute(\"data-composition-id\");\n const key = rootId && rootId in win.__timelines ? rootId : keys[keys.length - 1];\n const tl = win.__timelines[key];\n return { getDuration: () => tl.duration() };\n }\n }\n return null;\n };\n\n const adapter = getAdapter();\n if (adapter && adapter.getDuration() > 0) {\n clearInterval(this._probeInterval!);\n this._duration = adapter.getDuration();\n this._ready = true;\n this.controlsApi?.updateTime(0, this._duration);\n this.dispatchEvent(new CustomEvent(\"ready\", { detail: { duration: this._duration } }));\n\n // Auto-detect dimensions from composition\n const doc = this.iframe.contentDocument;\n const root = doc?.querySelector(\"[data-composition-id]\");\n if (root) {\n const w = parseInt(root.getAttribute(\"data-width\") || \"0\", 10);\n const h = parseInt(root.getAttribute(\"data-height\") || \"0\", 10);\n if (w > 0 && h > 0) {\n this._compositionWidth = w;\n this._compositionHeight = h;\n this._updateScale();\n }\n }\n\n this._setupParentMedia();\n\n if (this.hasAttribute(\"autoplay\")) {\n this.play();\n }\n return;\n }\n } catch {\n /* cross-origin */\n }\n\n if (attempts >= 40) {\n clearInterval(this._probeInterval!);\n this.dispatchEvent(\n new CustomEvent(\"error\", {\n detail: { message: \"Composition timeline not found after 8s\" },\n }),\n );\n }\n }, 200);\n }\n\n /** Inject the HyperFrames runtime into the iframe if not already present. */\n private _injectRuntime() {\n this._runtimeInjected = true;\n try {\n const doc = this.iframe.contentDocument;\n if (!doc) return;\n const script = doc.createElement(\"script\");\n script.src = RUNTIME_CDN_URL;\n script.onload = () => {\n // Runtime loaded — the probe interval will pick up __hf on next tick\n };\n script.onerror = () => {\n // CDN failed — the probe will continue and eventually timeout\n };\n (doc.head || doc.documentElement).appendChild(script);\n } catch {\n /* cross-origin — can't inject */\n }\n }\n\n private _updateScale() {\n const rect = this.getBoundingClientRect();\n if (rect.width === 0 || rect.height === 0) return;\n const scale = Math.min(\n rect.width / this._compositionWidth,\n rect.height / this._compositionHeight,\n );\n this.iframe.style.width = `${this._compositionWidth}px`;\n this.iframe.style.height = `${this._compositionHeight}px`;\n this.iframe.style.transform = `translate(-50%, -50%) scale(${scale})`;\n }\n\n private _setupControls() {\n if (this.controlsApi) return;\n const callbacks: ControlsCallbacks = {\n onPlay: () => this.play(),\n onPause: () => this.pause(),\n onSeek: (fraction) => this.seek(fraction * this._duration),\n onSpeedChange: (speed) => {\n this.playbackRate = speed;\n },\n };\n const presetsAttr = this.getAttribute(\"speed-presets\");\n const speedPresets = presetsAttr\n ? presetsAttr\n .split(\",\")\n .map(Number)\n .filter((n) => !isNaN(n) && n > 0)\n : undefined;\n this.controlsApi = createControls(this.shadow, callbacks, { speedPresets });\n }\n\n private _setupPoster() {\n const url = this.getAttribute(\"poster\");\n if (!url) {\n this.posterEl?.remove();\n this.posterEl = null;\n return;\n }\n if (!this.posterEl) {\n this.posterEl = document.createElement(\"img\");\n this.posterEl.className = \"hfp-poster\";\n this.shadow.appendChild(this.posterEl);\n }\n this.posterEl.src = url;\n }\n\n private _playParentMedia() {\n for (const m of this._parentMedia) {\n if (m.el.src) {\n m.el\n .play()\n .then(() => {\n // Parent play succeeded — mute the iframe copy to prevent double audio.\n // This runs asynchronously, so the runtime may briefly play both copies,\n // but the overlap is inaudible (same audio at the same position).\n this._muteIframeMedia();\n })\n .catch(() => {});\n }\n }\n }\n\n private _muteIframeMedia() {\n try {\n const doc = this.iframe.contentDocument;\n if (!doc) return;\n const mediaEls = doc.querySelectorAll<HTMLMediaElement>(\n \"audio[data-start], video[data-start]\",\n );\n for (const el of mediaEls) el.volume = 0;\n } catch {\n // cross-origin\n }\n }\n\n private _pauseParentMedia() {\n for (const m of this._parentMedia) m.el.pause();\n }\n\n /** Create a parent-frame media element, configure it, and start preloading. */\n private _createParentMedia(src: string, tag: \"audio\" | \"video\", start: number, duration: number) {\n // Deduplicate — browsers normalize URLs so we compare on the element after assignment\n if (this._parentMedia.some((m) => m.el.src === src)) return;\n\n const el = tag === \"video\" ? document.createElement(\"video\") : new Audio();\n el.preload = \"auto\";\n el.src = src;\n el.load();\n el.muted = this.muted;\n if (this.playbackRate !== 1) el.playbackRate = this.playbackRate;\n\n this._parentMedia.push({ el, start, duration });\n }\n\n /**\n * Set up a single parent-frame audio from an explicit URL (via `audio-src`).\n * Convenience for the common single-narration case — starts preloading\n * immediately without waiting for the iframe to load.\n */\n private _setupParentAudioFromUrl(audioSrc: string) {\n this._createParentMedia(audioSrc, \"audio\", 0, Infinity);\n }\n\n /**\n * Extract ALL timed media (audio/video with data-start) from the iframe's\n * DOM and create parent-frame copies. Disables the iframe originals so the\n * runtime doesn't try to play them (which would fail on mobile and cause\n * double playback on desktop).\n *\n * If `audio-src` was already set, this just disables the iframe media.\n */\n private _setupParentMedia() {\n try {\n const doc = this.iframe.contentDocument;\n if (!doc) return;\n\n // Find all timed media — matches the runtime's media.ts selector\n const mediaEls = doc.querySelectorAll<HTMLMediaElement>(\n \"audio[data-start], video[data-start]\",\n );\n\n for (const iframeEl of mediaEls) {\n const src = iframeEl.getAttribute(\"src\") || iframeEl.querySelector(\"source\")?.src;\n if (!src) continue;\n\n const start = parseFloat(iframeEl.getAttribute(\"data-start\") || \"0\");\n const duration = parseFloat(iframeEl.getAttribute(\"data-duration\") || \"Infinity\");\n const tag = iframeEl.tagName === \"VIDEO\" ? (\"video\" as const) : (\"audio\" as const);\n\n this._createParentMedia(src, tag, start, duration);\n\n // DO NOT strip data-start, data-duration, or src from the iframe elements.\n // The runtime's syncRuntimeMedia queries audio[data-start] — removing these\n // attributes makes the runtime unable to find, sync, or play media clips.\n // The iframe copies remain fully functional for the runtime. On mobile,\n // parent copies provide the audible output via the component's play() method.\n // On desktop and in the studio (which calls __player.play() directly),\n // the runtime's own media sync handles playback.\n }\n } catch {\n // Cross-origin iframe — can't access DOM, fall back to iframe media\n }\n }\n\n private _hidePoster() {\n this.posterEl?.remove();\n this.posterEl = null;\n }\n}\n\nif (!customElements.get(\"hyperframes-player\")) {\n customElements.define(\"hyperframes-player\", HyperframesPlayer);\n}\n\nexport { HyperframesPlayer };\nexport { formatTime, formatSpeed, SPEED_PRESETS } from \"./controls.js\";\nexport type { ControlsCallbacks, ControlsOptions } from \"./controls.js\";\n"],"mappings":"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","names":["PLAYER_STYLES","PLAY_ICON","PAUSE_ICON","SPEED_PRESETS","formatSpeed","speed","formatTime","seconds","s","m","sec","createControls","parent","callbacks","options","presets","controls","e","playBtn","PLAY_ICON","scrubber","progress","time","speedWrap","speedBtn","speedMenu","preset","item","isPlaying","hideTimeout","speedIndex","setActiveOption","opt","isOpen","target","newSpeed","onDocClick","handleScrubAt","clientX","rect","fraction","scrubbing","onMouseMove","onMouseUp","touch","onTouchMove","onTouchEnd","startHideTimer","host","current","duration","pct","playing","PAUSE_ICON","idx","DEFAULT_FPS","RUNTIME_CDN_URL","HyperframesPlayer","style","PLAYER_STYLES","event","m","name","_old","val","rate","timeInSeconds","frame","relTime","t","r","l","action","extra","target","data","wasPlaying","now","attempts","win","hasRuntime","hasTimelines","adapter","keys","rootId","key","tl","root","w","h","doc","script","rect","scale","callbacks","fraction","speed","presetsAttr","speedPresets","n","createControls","url","mediaEls","el","src","tag","start","duration","audioSrc","iframeEl"]}
|