@fuzionx/player 0.1.53 → 0.1.55
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/fuzionx-player.esm.js +5 -5
- package/dist/fuzionx-player.esm.js.map +2 -2
- package/dist/fuzionx-player.umd.js +2 -2
- package/dist/fuzionx-player.umd.js.map +2 -2
- package/dist/fx-player.esm.js +6 -0
- package/dist/fx-player.esm.js.map +7 -0
- package/dist/fx-player.umd.js +7 -0
- package/dist/fx-player.umd.js.map +7 -0
- package/package.json +1 -1
- package/src/FuzionXPublisher.js +0 -2
- package/src/FuzionXViewer.js +0 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
var u=[{urls:"stun:stun.l.google.com:19302"},{urls:"stun:stun1.l.google.com:19302"}],c={JOIN:"join",OFFER:"offer",ANSWER:"answer",CANDIDATE:"candidate",PLI:"pli",LEAVE:"leave",SLOT_INFO:"slot_info",CHAT:"chat",ERROR:"error"},l={BROADCAST:"broadcast",VIDEOCHAT:"videochat"},f={[l.BROADCAST]:1,[l.VIDEOCHAT]:9},_={MAX_RETRIES:5,BASE_DELAY_MS:1e3,MAX_DELAY_MS:3e4},S={VIDEO_MIME:"video/H264",AUDIO_MIME:"audio/opus",VIDEO_CLOCK:9e4,AUDIO_CLOCK:48e3};var d=class{constructor(e){this.url=e.url,this.onMessage=e.onMessage||(()=>{}),this.onOpen=e.onOpen||(()=>{}),this.onClose=e.onClose||(()=>{}),this.onError=e.onError||(()=>{}),this.autoReconnect=e.autoReconnect!==!1,this._ws=null,this._retryCount=0,this._reconnectTimer=null,this._intentionalClose=!1}connect(){this._intentionalClose=!1,this._doConnect()}_doConnect(){try{this._ws=new WebSocket(this.url)}catch(e){this.onError(e),this._scheduleReconnect();return}this._ws.onopen=()=>{this._retryCount=0,this.onOpen()},this._ws.onmessage=e=>{try{let t=JSON.parse(e.data);this.onMessage(t)}catch{console.warn("[FuzionX] Invalid JSON:",e.data)}},this._ws.onclose=e=>{this.onClose(e),!this._intentionalClose&&this.autoReconnect&&this._scheduleReconnect()},this._ws.onerror=e=>{this.onError(e)}}send(e){return this._ws&&this._ws.readyState===WebSocket.OPEN?(this._ws.send(JSON.stringify(e)),!0):!1}sendJoin(e,t,i={}){return this.send({type:c.JOIN,peer_id:e,channel_id:t,nickname:i.nickname||null,token:i.token||null,mode:i.mode||null})}sendOffer(e){return this.send({type:c.OFFER,sdp:e})}sendAnswer(e){return this.send({type:c.ANSWER,sdp:e})}sendCandidate(e){return this.send({type:c.CANDIDATE,candidate:e.candidate,sdp_mid:e.sdpMid,sdp_m_line_index:e.sdpMLineIndex})}sendChat(e,t){return this.send({type:c.CHAT,text:e,nickname:t||null,peer_id:null})}sendPLI(){return this.send({type:c.PLI})}sendLeave(){return this.send({type:c.LEAVE})}disconnect(){this._intentionalClose=!0,clearTimeout(this._reconnectTimer),this._ws&&(this._ws.close(),this._ws=null)}get connected(){return this._ws&&this._ws.readyState===WebSocket.OPEN}_scheduleReconnect(){if(this._retryCount>=_.MAX_RETRIES){console.error("[FuzionX] Max reconnect retries reached."),this.onError(new Error("Max reconnect retries"));return}let e=Math.min(_.BASE_DELAY_MS*Math.pow(2,this._retryCount),_.MAX_DELAY_MS);this._retryCount++,console.log(`[FuzionX] Reconnecting in ${e}ms (${this._retryCount}/${_.MAX_RETRIES})`),this._reconnectTimer=setTimeout(()=>this._doConnect(),e)}};var g=class p{constructor(e){this.url=e.url||null,this.hubUrl=e.hubUrl||null,this.channelId=e.channelId,this.mode=e.mode||l.BROADCAST,this.nickname=e.nickname||null,this.token=e.token||null,this.peerId=e.peerId||`viewer-${Math.random().toString(36).slice(2,10)}`,this.autoReconnect=e.autoReconnect!==!1,this.rtcConfig=e.rtcConfig||{iceServers:u,bundlePolicy:"max-bundle",rtcpMuxPolicy:"require"},this._signaling=null,this._pc=null,this._listeners={},this._slots=new Map,this._maxSlots=f[this.mode]||1,this._candidateQueue=[],this._connected=!1}on(e,t){return this._listeners[e]||(this._listeners[e]=[]),this._listeners[e].push(t),this}_emit(e,...t){(this._listeners[e]||[]).forEach(i=>i(...t))}async connect(){if(!this.url&&this.hubUrl)try{let e=await fetch(`${this.hubUrl}/api/channels/${this.channelId}`);if(!e.ok)throw new Error(`Channel not found: ${this.channelId}`);let t=await e.json();if(t.ws_url){let i=this.hubUrl.startsWith("https");this.url=t.ws_url.replace(/^ws(s?):/,i?"wss:":"ws:")}else{let
|
|
2
|
-
`),i=t.findIndex(n=>n.startsWith("m=video"));if(i!==-1){let n=[],s=new Map;if(t.forEach(
|
|
3
|
-
`)}_closePeerConnection(){this._pc&&(this._pc.close(),this._pc=null),this._candidateQueue=[]}};var w=class p{constructor(e){this.whipUrl=e.whipUrl||null,this.url=e.url||null,this.hubUrl=e.hubUrl||null,this.channelId=e.channelId||null,this.mode=e.mode||l.BROADCAST,this.nickname=e.nickname||null,this.token=e.token||null,this.peerId=e.peerId||`pub-${Math.random().toString(36).slice(2,10)}`,this.autoReconnect=e.autoReconnect!==!1,this.mediaConstraints=e.media||{video:!0,audio:!0},this._externalStream=e.stream||null,this.rtcConfig=e.rtcConfig||{iceServers:u,bundlePolicy:"max-bundle",rtcpMuxPolicy:"require"},this._signaling=null,this._pc=null,this._localStream=null,this._listeners={},this._candidateQueue=[],this._whipResourceUrl=null,this._maxSlots=f[this.mode]||1,this._slots=new Map,this._connected=!1}on(e,t){return this._listeners[e]||(this._listeners[e]=[]),this._listeners[e].push(t),this}_emit(e,...t){(this._listeners[e]||[]).forEach(i=>i(...t))}async connect(){try{if(!this.url&&!this.whipUrl&&this.hubUrl&&this.channelId){let e=await fetch(`${this.hubUrl}/api/channels`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({channel_id:this.channelId,source_type:"webrtc"})}),t;if(e.status===409){let i=await fetch(`${this.hubUrl}/api/channels/${this.channelId}`);if(!i.ok)throw new Error(`Channel not found: ${this.channelId}`);t=await i.json()}else if(e.ok)t=await e.json();else throw new Error(`Failed to create channel: ${this.channelId}`);if(t.ws_url){let i=this.hubUrl.startsWith("https");this.url=t.ws_url.replace(/^ws(s?):/,i?"wss:":"ws:")}else{let
|
|
4
|
-
`),i=t.findIndex(n=>n.startsWith("m=video"));if(i!==-1){let n=[],s=new Map;if(t.forEach(
|
|
5
|
-
`)}_onSignalingMessage(e){switch(e.type){case c.ANSWER:this._handleAnswer(e);break;case c.CANDIDATE:this._handleCandidate(e);break;case c.SLOT_INFO:this._handleSlotInfo(e);break;case c.CHAT:this._emit("chat",{peerId:e.peer_id,nickname:e.nickname,text:e.text});break;case c.ERROR:this._emit("error",new Error(e.message));break}}async _handleAnswer(e){if(this._pc)try{await this._pc.setRemoteDescription(new RTCSessionDescription({type:"answer",sdp:e.sdp}));for(let t of this._candidateQueue)await this._pc.addIceCandidate(t);this._candidateQueue=[]}catch(t){this._emit("error",t)}}async _handleCandidate(e){let t=new RTCIceCandidate({candidate:e.candidate,sdpMid:e.sdp_mid,sdpMLineIndex:e.sdp_m_line_index});if(this._pc&&this._pc.remoteDescription)try{await this._pc.addIceCandidate(t)}catch{}else this._candidateQueue.push(t)}_handleSlotInfo(e){if(e.sender_id===this.peerId)return;let t=parseInt(e.stream_id.replace("stream_",""),10);if(!e.nickname||e.nickname===""){let
|
|
1
|
+
var u=[{urls:"stun:stun.l.google.com:19302"},{urls:"stun:stun1.l.google.com:19302"}],c={JOIN:"join",OFFER:"offer",ANSWER:"answer",CANDIDATE:"candidate",PLI:"pli",LEAVE:"leave",SLOT_INFO:"slot_info",CHAT:"chat",ERROR:"error"},l={BROADCAST:"broadcast",VIDEOCHAT:"videochat"},f={[l.BROADCAST]:1,[l.VIDEOCHAT]:9},_={MAX_RETRIES:5,BASE_DELAY_MS:1e3,MAX_DELAY_MS:3e4},S={VIDEO_MIME:"video/H264",AUDIO_MIME:"audio/opus",VIDEO_CLOCK:9e4,AUDIO_CLOCK:48e3};var d=class{constructor(e){this.url=e.url,this.onMessage=e.onMessage||(()=>{}),this.onOpen=e.onOpen||(()=>{}),this.onClose=e.onClose||(()=>{}),this.onError=e.onError||(()=>{}),this.autoReconnect=e.autoReconnect!==!1,this._ws=null,this._retryCount=0,this._reconnectTimer=null,this._intentionalClose=!1}connect(){this._intentionalClose=!1,this._doConnect()}_doConnect(){try{this._ws=new WebSocket(this.url)}catch(e){this.onError(e),this._scheduleReconnect();return}this._ws.onopen=()=>{this._retryCount=0,this.onOpen()},this._ws.onmessage=e=>{try{let t=JSON.parse(e.data);this.onMessage(t)}catch{console.warn("[FuzionX] Invalid JSON:",e.data)}},this._ws.onclose=e=>{this.onClose(e),!this._intentionalClose&&this.autoReconnect&&this._scheduleReconnect()},this._ws.onerror=e=>{this.onError(e)}}send(e){return this._ws&&this._ws.readyState===WebSocket.OPEN?(this._ws.send(JSON.stringify(e)),!0):!1}sendJoin(e,t,i={}){return this.send({type:c.JOIN,peer_id:e,channel_id:t,nickname:i.nickname||null,token:i.token||null,mode:i.mode||null})}sendOffer(e){return this.send({type:c.OFFER,sdp:e})}sendAnswer(e){return this.send({type:c.ANSWER,sdp:e})}sendCandidate(e){return this.send({type:c.CANDIDATE,candidate:e.candidate,sdp_mid:e.sdpMid,sdp_m_line_index:e.sdpMLineIndex})}sendChat(e,t){return this.send({type:c.CHAT,text:e,nickname:t||null,peer_id:null})}sendPLI(){return this.send({type:c.PLI})}sendLeave(){return this.send({type:c.LEAVE})}disconnect(){this._intentionalClose=!0,clearTimeout(this._reconnectTimer),this._ws&&(this._ws.close(),this._ws=null)}get connected(){return this._ws&&this._ws.readyState===WebSocket.OPEN}_scheduleReconnect(){if(this._retryCount>=_.MAX_RETRIES){console.error("[FuzionX] Max reconnect retries reached."),this.onError(new Error("Max reconnect retries"));return}let e=Math.min(_.BASE_DELAY_MS*Math.pow(2,this._retryCount),_.MAX_DELAY_MS);this._retryCount++,console.log(`[FuzionX] Reconnecting in ${e}ms (${this._retryCount}/${_.MAX_RETRIES})`),this._reconnectTimer=setTimeout(()=>this._doConnect(),e)}};var g=class p{constructor(e){this.url=e.url||null,this.hubUrl=e.hubUrl||null,this.channelId=e.channelId,this.mode=e.mode||l.BROADCAST,this.nickname=e.nickname||null,this.token=e.token||null,this.peerId=e.peerId||`viewer-${Math.random().toString(36).slice(2,10)}`,this.autoReconnect=e.autoReconnect!==!1,this.rtcConfig=e.rtcConfig||{iceServers:u,bundlePolicy:"max-bundle",rtcpMuxPolicy:"require"},this._signaling=null,this._pc=null,this._listeners={},this._slots=new Map,this._maxSlots=f[this.mode]||1,this._candidateQueue=[],this._connected=!1}on(e,t){return this._listeners[e]||(this._listeners[e]=[]),this._listeners[e].push(t),this}_emit(e,...t){(this._listeners[e]||[]).forEach(i=>i(...t))}async connect(){if(!this.url&&this.hubUrl)try{let e=await fetch(`${this.hubUrl}/api/channels/${this.channelId}`);if(!e.ok)throw new Error(`Channel not found: ${this.channelId}`);let t=await e.json();if(t.ws_url){let i=this.hubUrl.startsWith("https");this.url=t.ws_url.replace(/^ws(s?):/,i?"wss:":"ws:")}else{let a=this.hubUrl.startsWith("https")?"wss":"ws";this.url=`${a}://${t.media_ip}:${t.webrtc_port}`}}catch(e){this._emit("error",e);return}if(!this.url){this._emit("error",new Error("url \uB610\uB294 hubUrl\uC744 \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4."));return}this._signaling=new d({url:this.url,autoReconnect:this.autoReconnect,onOpen:()=>this._onSignalingOpen(),onMessage:e=>this._onSignalingMessage(e),onClose:e=>this._onSignalingClose(e),onError:e=>this._emit("error",e)}),this._signaling.connect(),this._beforeUnloadHandler=()=>{this._signaling&&this._signaling.connected&&this._signaling.sendLeave(),this._closePeerConnection()},window.addEventListener("beforeunload",this._beforeUnloadHandler)}async disconnect(){this._beforeUnloadHandler&&(window.removeEventListener("beforeunload",this._beforeUnloadHandler),this._beforeUnloadHandler=null),this._signaling&&(this._signaling.sendLeave(),await new Promise(e=>setTimeout(e,100)),this._signaling.disconnect()),this._closePeerConnection(),this._slots.clear(),this._connected=!1}chat(e){this._signaling&&this._signaling.sendChat(e,this.nickname)}requestKeyframe(){this._signaling&&this._signaling.sendPLI()}get slots(){return this._slots}_onSignalingOpen(){this._signaling.sendJoin(this.peerId,this.channelId,{nickname:this.nickname,token:this.token,mode:this.mode}),this._createPeerConnection().catch(e=>this._emit("error",e))}_onSignalingMessage(e){switch(e.type){case c.ANSWER:this._handleAnswer(e);break;case c.CANDIDATE:this._handleCandidate(e);break;case c.SLOT_INFO:this._handleSlotInfo(e);break;case c.CHAT:this._emit("chat",{peerId:e.peer_id,nickname:e.nickname,text:e.text});break;case c.ERROR:this._emit("error",new Error(e.message));break}}_onSignalingClose(e){this._closePeerConnection(),this._connected=!1,this._emit("close",e)}async _handleAnswer(e){if(this._pc)try{await this._pc.setRemoteDescription(new RTCSessionDescription({type:"answer",sdp:e.sdp}));for(let t of this._candidateQueue)await this._pc.addIceCandidate(t);this._candidateQueue=[]}catch(t){this._emit("error",t)}}async _handleCandidate(e){let t=new RTCIceCandidate({candidate:e.candidate,sdpMid:e.sdp_mid,sdpMLineIndex:e.sdp_m_line_index});if(this._pc&&this._pc.remoteDescription)try{await this._pc.addIceCandidate(t)}catch(i){console.warn("[FuzionX] ICE candidate error:",i)}else this._candidateQueue.push(t)}_handleSlotInfo(e){let t=parseInt(e.stream_id.replace("stream_",""),10);if(!e.nickname||e.nickname===""){let a=this._slots.get(t);a&&this._slots.set(t,{slotIndex:t,stream:a.stream}),this._emit("slot_remove",{slotIndex:t,senderId:e.sender_id});return}let i=this._slots.get(t)||{};this._slots.set(t,{...i,slotIndex:t,streamId:e.stream_id,nickname:e.nickname,senderId:e.sender_id}),this._emit("slot",this._slots.get(t))}async _createPeerConnection(){this._pc=new RTCPeerConnection(this.rtcConfig);let e=0;this._pc.ontrack=a=>{let n=a.track;if(n.kind==="video"){let s=e++,r=a.streams[0];r||(r=new MediaStream,r.addTrack(n));let o=this._slots.get(s)||{slotIndex:s};o.stream=r,this._slots.set(s,o),this._emit("stream",r,s),this._connected||(this._connected=!0,this._emit("connected"),this._signaling&&this._signaling.sendPLI())}},this._pc.onconnectionstatechange=()=>{let a=this._pc?.connectionState;(a==="failed"||a==="disconnected")&&this._emit("error",new Error(`PeerConnection ${a}`))};for(let a=0;a<this._maxSlots;a++)this._pc.addTransceiver("video",{direction:"recvonly"}),this._pc.addTransceiver("audio",{direction:"recvonly"});let t=await this._pc.createOffer();t.sdp=p._forceCodecs(t.sdp),await this._pc.setLocalDescription(t),await this._waitForIceGathering();let i=this._pc.localDescription?.sdp;i&&this._signaling.sendOffer(i)}_waitForIceGathering(){return new Promise(e=>{if(this._pc.iceGatheringState==="complete")return e();let t=()=>{this._pc?.iceGatheringState==="complete"&&(this._pc.removeEventListener("icegatheringstatechange",t),e())};this._pc.addEventListener("icegatheringstatechange",t),setTimeout(()=>{this._pc&&this._pc.removeEventListener("icegatheringstatechange",t),e()},150)})}static _forceCodecs(e){let t=e.split(`\r
|
|
2
|
+
`),i=t.findIndex(n=>n.startsWith("m=video"));if(i!==-1){let n=[],s=new Map;if(t.forEach(r=>{let o=r.match(/a=rtpmap:(\d+) H264\/90000/);o&&(n.push(o[1]),s.set(o[1],0))}),t.forEach(r=>{if(r.startsWith("a=fmtp:")){let o=r.split(" ")[0].split(":")[1];s.has(o)&&(r.includes("profile-level-id=42e01f")?s.set(o,100):r.includes("profile-level-id=42001f")&&s.set(o,80),r.includes("packetization-mode=1")&&s.set(o,(s.get(o)||0)+10))}}),n.length>0){n.sort((h,m)=>s.get(m)-s.get(h));let r=t[i].split(" "),o=r.slice(3).filter(h=>!n.includes(h));t[i]=[...r.slice(0,3),...n,...o].join(" ")}}let a=t.findIndex(n=>n.startsWith("m=audio"));if(a!==-1){let n=[];if(t.forEach(s=>{let r=s.match(/a=rtpmap:(\d+) opus\/48000/);r&&n.push(r[1])}),n.length>0){let s=t[a].split(" "),r=s.slice(3).filter(o=>!n.includes(o));t[a]=[...s.slice(0,3),...n,...r].join(" ")}}return t.join(`\r
|
|
3
|
+
`)}_closePeerConnection(){this._pc&&(this._pc.close(),this._pc=null),this._candidateQueue=[]}};var w=class p{constructor(e){this.whipUrl=e.whipUrl||null,this.url=e.url||null,this.hubUrl=e.hubUrl||null,this.channelId=e.channelId||null,this.mode=e.mode||l.BROADCAST,this.nickname=e.nickname||null,this.token=e.token||null,this.peerId=e.peerId||`pub-${Math.random().toString(36).slice(2,10)}`,this.autoReconnect=e.autoReconnect!==!1,this.mediaConstraints=e.media||{video:!0,audio:!0},this._externalStream=e.stream||null,this.rtcConfig=e.rtcConfig||{iceServers:u,bundlePolicy:"max-bundle",rtcpMuxPolicy:"require"},this._signaling=null,this._pc=null,this._localStream=null,this._listeners={},this._candidateQueue=[],this._whipResourceUrl=null,this._maxSlots=f[this.mode]||1,this._slots=new Map,this._connected=!1}on(e,t){return this._listeners[e]||(this._listeners[e]=[]),this._listeners[e].push(t),this}_emit(e,...t){(this._listeners[e]||[]).forEach(i=>i(...t))}async connect(){try{if(!this.url&&!this.whipUrl&&this.hubUrl&&this.channelId){let e=await fetch(`${this.hubUrl}/api/channels`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({channel_id:this.channelId,source_type:"webrtc"})}),t;if(e.status===409){let i=await fetch(`${this.hubUrl}/api/channels/${this.channelId}`);if(!i.ok)throw new Error(`Channel not found: ${this.channelId}`);t=await i.json()}else if(e.ok)t=await e.json();else throw new Error(`Failed to create channel: ${this.channelId}`);if(t.ws_url){let i=this.hubUrl.startsWith("https");this.url=t.ws_url.replace(/^ws(s?):/,i?"wss:":"ws:")}else{let a=this.hubUrl.startsWith("https")?"wss":"ws";this.url=`${a}://${t.media_ip}:${t.webrtc_port}`}}if(await this._acquireMedia(),this.whipUrl)await this._connectWhip();else if(this.url)this._connectWebSocket();else throw new Error("url, hubUrl, \uB610\uB294 whipUrl \uC911 \uD558\uB098\uB97C \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4.");this._beforeUnloadHandler=()=>{this._signaling&&this._signaling.connected&&this._signaling.sendLeave(),this._closePeerConnection(),this._stopMedia()},window.addEventListener("beforeunload",this._beforeUnloadHandler)}catch(e){this._emit("error",e)}}async disconnect(){if(this._beforeUnloadHandler&&(window.removeEventListener("beforeunload",this._beforeUnloadHandler),this._beforeUnloadHandler=null),this.whipUrl&&this._whipResourceUrl){try{let e=new URL(this.whipUrl).origin;await fetch(`${e}${this._whipResourceUrl}`,{method:"DELETE"})}catch(e){console.warn("[FuzionX] WHIP DELETE error:",e)}this._whipResourceUrl=null}this._signaling&&(this._signaling.sendLeave(),await new Promise(e=>setTimeout(e,100)),this._signaling.disconnect()),this._closePeerConnection(),this._stopMedia(),this._connected=!1}chat(e){this._signaling&&this._signaling.sendChat(e,this.nickname)}get localStream(){return this._localStream}get slots(){return this._slots}async _acquireMedia(){this._externalStream?this._localStream=this._externalStream:this._localStream=await navigator.mediaDevices.getUserMedia(this.mediaConstraints),this._emit("media",this._localStream)}_stopMedia(){this._localStream&&!this._externalStream&&this._localStream.getTracks().forEach(e=>e.stop()),this._localStream=null}async _connectWhip(){this._pc=new RTCPeerConnection(this.rtcConfig),this._localStream.getTracks().forEach(s=>{this._pc.addTrack(s,this._localStream)});let e=await this._pc.createOffer();e.sdp=p._forceCodecs(e.sdp),await this._pc.setLocalDescription(e),await new Promise(s=>{this._pc.iceGatheringState==="complete"?s():(this._pc.onicegatheringstatechange=()=>{this._pc.iceGatheringState==="complete"&&s()},setTimeout(s,150))});let t=this._pc.localDescription,i=this.whipUrl;this.token&&!i.includes("token=")&&(i+=(i.includes("?")?"&":"?")+`token=${this.token}`);let a=await fetch(i,{method:"POST",headers:{"Content-Type":"application/sdp"},body:t.sdp});if(a.status!==201)throw new Error(`WHIP failed: ${a.status} ${await a.text()}`);let n=await a.text();this._whipResourceUrl=a.headers.get("location"),await this._pc.setRemoteDescription(new RTCSessionDescription({type:"answer",sdp:n})),this._pc.onconnectionstatechange=()=>{let s=this._pc?.connectionState;s==="connected"?(this._connected=!0,this._emit("ready")):(s==="failed"||s==="disconnected")&&this._emit("error",new Error(`WHIP PeerConnection ${s}`))},this._emit("ready")}_connectWebSocket(){this._signaling=new d({url:this.url,autoReconnect:this.autoReconnect,onOpen:()=>this._onSignalingOpen(),onMessage:e=>this._onSignalingMessage(e),onClose:e=>{this._closePeerConnection(),this._connected=!1,this._emit("close",e)},onError:e=>this._emit("error",e)}),this._signaling.connect()}async _onSignalingOpen(){this._signaling.sendJoin(this.peerId,this.channelId,{nickname:this.nickname,token:this.token,mode:this.mode}),await this._createPeerConnection()}async _createPeerConnection(){if(this._pc=new RTCPeerConnection(this.rtcConfig),this._localStream.getTracks().forEach(i=>{this._pc.addTrack(i,this._localStream)}),this.mode===l.VIDEOCHAT){for(let n=0;n<this._maxSlots;n++)this._pc.addTransceiver("video",{direction:"recvonly"}),this._pc.addTransceiver("audio",{direction:"recvonly"});this._pc.getTransceivers().forEach(n=>{n.sender.track&&n.direction==="recvonly"&&(n.direction="sendrecv")});let a=0;this._pc.ontrack=n=>{let s=n.track;if(s.kind==="video"){let r=a++,o=n.streams[0];o||(o=new MediaStream,o.addTrack(s));let h=this._slots.get(r)||{slotIndex:r};h.stream=o,this._slots.set(r,h),this._emit("stream",o,r)}}}this._pc.onconnectionstatechange=()=>{let i=this._pc?.connectionState;i==="connected"&&!this._connected?(this._connected=!0,this._emit("ready")):(i==="failed"||i==="disconnected")&&this._emit("error",new Error(`PeerConnection ${i}`))};let e=await this._pc.createOffer();e.sdp=p._forceCodecs(e.sdp),await this._pc.setLocalDescription(e),await this._waitForIceGathering();let t=this._pc.localDescription?.sdp;t&&this._signaling.sendOffer(t)}_waitForIceGathering(){return new Promise(e=>{if(this._pc.iceGatheringState==="complete")return e();let t=()=>{this._pc?.iceGatheringState==="complete"&&(this._pc.removeEventListener("icegatheringstatechange",t),e())};this._pc.addEventListener("icegatheringstatechange",t),setTimeout(()=>{this._pc&&this._pc.removeEventListener("icegatheringstatechange",t),e()},150)})}static _forceCodecs(e){let t=e.split(`\r
|
|
4
|
+
`),i=t.findIndex(n=>n.startsWith("m=video"));if(i!==-1){let n=[],s=new Map;if(t.forEach(r=>{let o=r.match(/a=rtpmap:(\d+) H264\/90000/);o&&(n.push(o[1]),s.set(o[1],0))}),t.forEach(r=>{if(r.startsWith("a=fmtp:")){let o=r.split(" ")[0].split(":")[1];s.has(o)&&(r.includes("profile-level-id=42e01f")?s.set(o,100):r.includes("profile-level-id=42001f")&&s.set(o,80),r.includes("packetization-mode=1")&&s.set(o,(s.get(o)||0)+10))}}),n.length>0){n.sort((h,m)=>s.get(m)-s.get(h));let r=t[i].split(" "),o=r.slice(3).filter(h=>!n.includes(h));t[i]=[...r.slice(0,3),...n,...o].join(" ")}}let a=t.findIndex(n=>n.startsWith("m=audio"));if(a!==-1){let n=[];if(t.forEach(s=>{let r=s.match(/a=rtpmap:(\d+) opus\/48000/);r&&n.push(r[1])}),n.length>0){let s=t[a].split(" "),r=s.slice(3).filter(o=>!n.includes(o));t[a]=[...s.slice(0,3),...n,...r].join(" ")}}return t.join(`\r
|
|
5
|
+
`)}_onSignalingMessage(e){switch(e.type){case c.ANSWER:this._handleAnswer(e);break;case c.CANDIDATE:this._handleCandidate(e);break;case c.SLOT_INFO:this._handleSlotInfo(e);break;case c.CHAT:this._emit("chat",{peerId:e.peer_id,nickname:e.nickname,text:e.text});break;case c.ERROR:this._emit("error",new Error(e.message));break}}async _handleAnswer(e){if(this._pc)try{await this._pc.setRemoteDescription(new RTCSessionDescription({type:"answer",sdp:e.sdp}));for(let t of this._candidateQueue)await this._pc.addIceCandidate(t);this._candidateQueue=[]}catch(t){this._emit("error",t)}}async _handleCandidate(e){let t=new RTCIceCandidate({candidate:e.candidate,sdpMid:e.sdp_mid,sdpMLineIndex:e.sdp_m_line_index});if(this._pc&&this._pc.remoteDescription)try{await this._pc.addIceCandidate(t)}catch{}else this._candidateQueue.push(t)}_handleSlotInfo(e){if(e.sender_id===this.peerId)return;let t=parseInt(e.stream_id.replace("stream_",""),10);if(!e.nickname||e.nickname===""){let a=this._slots.get(t);a&&this._slots.set(t,{slotIndex:t,stream:a.stream}),this._emit("slot_remove",{slotIndex:t,senderId:e.sender_id});return}let i=this._slots.get(t)||{};this._slots.set(t,{...i,slotIndex:t,streamId:e.stream_id,nickname:e.nickname,senderId:e.sender_id}),this._emit("slot",this._slots.get(t))}_closePeerConnection(){this._pc&&(this._pc.close(),this._pc=null),this._candidateQueue=[]}};export{S as CODEC,u as DEFAULT_ICE_SERVERS,w as FuzionXPublisher,d as FuzionXSignaling,g as FuzionXViewer,f as MAX_SLOTS,_ as RECONNECT,l as SessionMode,c as SignalType};
|
|
6
6
|
//# sourceMappingURL=fuzionx-player.esm.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/constants.js", "../src/FuzionXSignaling.js", "../src/FuzionXViewer.js", "../src/FuzionXPublisher.js"],
|
|
4
|
-
"sourcesContent": ["/**\n * @fuzionx/player \u2014 Constants & Default Configuration\n */\n\n/** Default ICE Servers */\nexport const DEFAULT_ICE_SERVERS = [\n { urls: 'stun:stun.l.google.com:19302' },\n { urls: 'stun:stun1.l.google.com:19302' },\n];\n\n/** Signaling message types (maps to server SignalMessage enum) */\nexport const SignalType = {\n JOIN: 'join',\n OFFER: 'offer',\n ANSWER: 'answer',\n CANDIDATE: 'candidate',\n PLI: 'pli',\n LEAVE: 'leave',\n SLOT_INFO: 'slot_info',\n CHAT: 'chat',\n ERROR: 'error',\n};\n\n/** Session modes */\nexport const SessionMode = {\n BROADCAST: 'broadcast',\n VIDEOCHAT: 'videochat',\n};\n\n/** Max slots per mode */\nexport const MAX_SLOTS = {\n [SessionMode.BROADCAST]: 1,\n [SessionMode.VIDEOCHAT]: 9,\n};\n\n/** Default reconnect settings */\nexport const RECONNECT = {\n MAX_RETRIES: 5,\n BASE_DELAY_MS: 1000,\n MAX_DELAY_MS: 30000,\n};\n\n/** Codec preferences */\nexport const CODEC = {\n VIDEO_MIME: 'video/H264',\n AUDIO_MIME: 'audio/opus',\n VIDEO_CLOCK: 90000,\n AUDIO_CLOCK: 48000,\n};\n", "/**\n * @fuzionx/player \u2014 WebSocket Signaling Layer\n *\n * FuzionX Media Server\uC758 JSON \uC2DC\uADF8\uB110\uB9C1 \uD504\uB85C\uD1A0\uCF5C \uCEA1\uC290\uD654.\n * \uC790\uB3D9 \uC7AC\uC5F0\uACB0 + \uC774\uBCA4\uD2B8 \uC2DC\uC2A4\uD15C.\n */\n\nimport { SignalType, RECONNECT } from './constants.js';\n\nexport class FuzionXSignaling {\n /**\n * @param {Object} opts\n * @param {string} opts.url - WebSocket URL (ws:// or wss://)\n * @param {Function} [opts.onMessage] - \uBA54\uC2DC\uC9C0 \uC218\uC2E0 \uCF5C\uBC31\n * @param {Function} [opts.onOpen] - \uC5F0\uACB0 \uC131\uACF5 \uCF5C\uBC31\n * @param {Function} [opts.onClose] - \uC5F0\uACB0 \uC885\uB8CC \uCF5C\uBC31\n * @param {Function} [opts.onError] - \uC5D0\uB7EC \uCF5C\uBC31\n * @param {boolean} [opts.autoReconnect=true]\n */\n constructor(opts) {\n this.url = opts.url;\n this.onMessage = opts.onMessage || (() => {});\n this.onOpen = opts.onOpen || (() => {});\n this.onClose = opts.onClose || (() => {});\n this.onError = opts.onError || (() => {});\n this.autoReconnect = opts.autoReconnect !== false;\n\n /** @private */\n this._ws = null;\n this._retryCount = 0;\n this._reconnectTimer = null;\n this._intentionalClose = false;\n }\n\n /** WebSocket \uC5F0\uACB0. */\n connect() {\n this._intentionalClose = false;\n this._doConnect();\n }\n\n /** @private */\n _doConnect() {\n try {\n this._ws = new WebSocket(this.url);\n } catch (e) {\n this.onError(e);\n this._scheduleReconnect();\n return;\n }\n\n this._ws.onopen = () => {\n this._retryCount = 0;\n this.onOpen();\n };\n\n this._ws.onmessage = (event) => {\n try {\n const msg = JSON.parse(event.data);\n this.onMessage(msg);\n } catch (e) {\n console.warn('[FuzionX] Invalid JSON:', event.data);\n }\n };\n\n this._ws.onclose = (event) => {\n this.onClose(event);\n if (!this._intentionalClose && this.autoReconnect) {\n this._scheduleReconnect();\n }\n };\n\n this._ws.onerror = (event) => {\n this.onError(event);\n };\n }\n\n /** JSON \uBA54\uC2DC\uC9C0 \uC804\uC1A1. */\n send(msg) {\n if (this._ws && this._ws.readyState === WebSocket.OPEN) {\n this._ws.send(JSON.stringify(msg));\n return true;\n }\n return false;\n }\n\n // \u2500\u2500 \uC2DC\uADF8\uB110\uB9C1 \uD5EC\uD37C \u2500\u2500\n\n /** Join \uC804\uC1A1 */\n sendJoin(peerId, channelId, opts = {}) {\n return this.send({\n type: SignalType.JOIN,\n peer_id: peerId,\n channel_id: channelId,\n nickname: opts.nickname || null,\n token: opts.token || null,\n mode: opts.mode || null,\n });\n }\n\n /** SDP Offer \uC804\uC1A1 */\n sendOffer(sdp) {\n return this.send({ type: SignalType.OFFER, sdp });\n }\n\n /** SDP Answer \uC804\uC1A1 */\n sendAnswer(sdp) {\n return this.send({ type: SignalType.ANSWER, sdp });\n }\n\n /** ICE Candidate \uC804\uC1A1 */\n sendCandidate(candidate) {\n return this.send({\n type: SignalType.CANDIDATE,\n candidate: candidate.candidate,\n sdp_mid: candidate.sdpMid,\n sdp_m_line_index: candidate.sdpMLineIndex,\n });\n }\n\n /** Chat \uC804\uC1A1 */\n sendChat(text, nickname) {\n return this.send({\n type: SignalType.CHAT,\n text,\n nickname: nickname || null,\n peer_id: null,\n });\n }\n\n /** PLI \uC694\uCCAD */\n sendPLI() {\n return this.send({ type: SignalType.PLI });\n }\n\n /** Leave \uC804\uC1A1 */\n sendLeave() {\n return this.send({ type: SignalType.LEAVE });\n }\n\n /** \uC5F0\uACB0 \uC885\uB8CC (\uC7AC\uC5F0\uACB0 \uC548 \uD568). */\n disconnect() {\n this._intentionalClose = true;\n clearTimeout(this._reconnectTimer);\n if (this._ws) {\n this._ws.close();\n this._ws = null;\n }\n }\n\n /** @returns {boolean} \uC5F0\uACB0 \uC0C1\uD0DC */\n get connected() {\n return this._ws && this._ws.readyState === WebSocket.OPEN;\n }\n\n /** @private \uC7AC\uC5F0\uACB0 \uC2A4\uCF00\uC904 (Exponential Backoff). */\n _scheduleReconnect() {\n if (this._retryCount >= RECONNECT.MAX_RETRIES) {\n console.error('[FuzionX] Max reconnect retries reached.');\n this.onError(new Error('Max reconnect retries'));\n return;\n }\n const delay = Math.min(\n RECONNECT.BASE_DELAY_MS * Math.pow(2, this._retryCount),\n RECONNECT.MAX_DELAY_MS\n );\n this._retryCount++;\n console.log(`[FuzionX] Reconnecting in ${delay}ms (${this._retryCount}/${RECONNECT.MAX_RETRIES})`);\n this._reconnectTimer = setTimeout(() => this._doConnect(), delay);\n }\n}\n", "/**\n * @fuzionx/player \u2014 FuzionXViewer (\uC218\uC2E0\uC790 \uBAA8\uB4DC)\n *\n * \uC11C\uBC84\uC5D0\uC11C MediaStream\uC744 \uC218\uC2E0\uD558\uC5EC <video> \uC5D8\uB9AC\uBA3C\uD2B8\uC5D0 \uB80C\uB354\uB9C1.\n * broadcast(1 stream) / videochat(\uCD5C\uB300 9 streams) \uC9C0\uC6D0.\n *\n * @example\n * const viewer = new FuzionXViewer({\n * url: 'wss://media:50002',\n * channelId: 'my-live',\n * mode: 'broadcast',\n * });\n * viewer.on('stream', (stream, slotIndex) => {\n * videoEl.srcObject = stream;\n * });\n * viewer.connect();\n */\n\nimport { FuzionXSignaling } from './FuzionXSignaling.js';\nimport { DEFAULT_ICE_SERVERS, SignalType, SessionMode, MAX_SLOTS } from './constants.js';\n\nexport class FuzionXViewer {\n /**\n * @param {Object} opts\n * @param {string} [opts.url] - WebSocket URL (\uC9C1\uC811 \uC9C0\uC815)\n * @param {string} [opts.hubUrl] - Hub API URL (\uC790\uB3D9 \uB77C\uC6B0\uD305: Hub\uC5D0\uC11C \uBBF8\uB514\uC5B4 \uC11C\uBC84 \uC870\uD68C)\n * @param {string} opts.channelId - \uCC44\uB110 ID\n * @param {string} [opts.mode='broadcast'] - 'broadcast' | 'videochat'\n * @param {string} [opts.nickname] - \uB2C9\uB124\uC784\n * @param {string} [opts.token] - \uC778\uC99D \uD1A0\uD070\n * @param {string} [opts.peerId] - \uD53C\uC5B4 ID (\uC790\uB3D9 \uC0DD\uC131)\n * @param {RTCConfiguration} [opts.rtcConfig] - WebRTC \uC124\uC815 \uC624\uBC84\uB77C\uC774\uB4DC\n * @param {boolean} [opts.autoReconnect=true]\n */\n constructor(opts) {\n this.url = opts.url || null;\n this.hubUrl = opts.hubUrl || null;\n this.channelId = opts.channelId;\n this.mode = opts.mode || SessionMode.BROADCAST;\n this.nickname = opts.nickname || null;\n this.token = opts.token || null;\n this.peerId = opts.peerId || `viewer-${Math.random().toString(36).slice(2, 10)}`;\n this.autoReconnect = opts.autoReconnect !== false;\n\n this.rtcConfig = opts.rtcConfig || {\n iceServers: DEFAULT_ICE_SERVERS,\n bundlePolicy: 'max-bundle',\n rtcpMuxPolicy: 'require',\n };\n\n /** @private */\n this._signaling = null;\n this._pc = null;\n this._listeners = {};\n this._slots = new Map(); // slotIndex \u2192 { streamId, nickname, senderId, stream }\n this._maxSlots = MAX_SLOTS[this.mode] || 1;\n this._candidateQueue = [];\n this._connected = false;\n }\n\n // \u2500\u2500 Event System \u2500\u2500\n\n /**\n * \uC774\uBCA4\uD2B8 \uB9AC\uC2A4\uB108 \uB4F1\uB85D.\n * @param {'stream'|'slot'|'slot_remove'|'chat'|'error'|'close'|'connected'} event\n * @param {Function} handler\n */\n on(event, handler) {\n if (!this._listeners[event]) this._listeners[event] = [];\n this._listeners[event].push(handler);\n return this;\n }\n\n /** @private */\n _emit(event, ...args) {\n (this._listeners[event] || []).forEach((fn) => fn(...args));\n }\n\n // \u2500\u2500 Lifecycle \u2500\u2500\n\n /** \uC11C\uBC84 \uC5F0\uACB0 + WebRTC \uC138\uC158 \uC2DC\uC791. */\n async connect() {\n // Hub \uB77C\uC6B0\uD305: hubUrl\uC774 \uC788\uC73C\uBA74 Hub\uC5D0\uC11C \uBBF8\uB514\uC5B4 \uC11C\uBC84 \uC870\uD68C\n if (!this.url && this.hubUrl) {\n try {\n const res = await fetch(`${this.hubUrl}/api/channels/${this.channelId}`);\n if (!res.ok) throw new Error(`Channel not found: ${this.channelId}`);\n const data = await res.json();\n // ws_url \uC0AC\uC6A9 \uB610\uB294 ip:port\uC5D0\uC11C \uC0DD\uC131\n if (data.ws_url) {\n // Hub\uC640 \uAC19\uC740 \uD504\uB85C\uD1A0\uCF5C \uC0AC\uC6A9 (https\u2192wss, http\u2192ws)\n const isSecure = this.hubUrl.startsWith('https');\n this.url = data.ws_url.replace(/^ws(s?):/, isSecure ? 'wss:' : 'ws:');\n } else {\n const isSecure = this.hubUrl.startsWith('https');\n const wsProto = isSecure ? 'wss' : 'ws';\n this.url = `${wsProto}://${data.media_ip}:${data.webrtc_port}`;\n }\n } catch (e) {\n this._emit('error', e);\n return;\n }\n }\n\n if (!this.url) {\n this._emit('error', new Error('url \uB610\uB294 hubUrl\uC744 \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4.'));\n return;\n }\n\n this._signaling = new FuzionXSignaling({\n url: this.url,\n autoReconnect: this.autoReconnect,\n onOpen: () => this._onSignalingOpen(),\n onMessage: (msg) => this._onSignalingMessage(msg),\n onClose: (evt) => this._onSignalingClose(evt),\n onError: (err) => this._emit('error', err),\n });\n this._signaling.connect();\n\n // \uD398\uC774\uC9C0 \uC774\uB3D9/\uD0ED \uB2EB\uAE30 \uC2DC \uC880\uBE44 \uBC29\uC9C0\n this._beforeUnloadHandler = () => {\n if (this._signaling && this._signaling.connected) {\n this._signaling.sendLeave();\n }\n this._closePeerConnection();\n };\n window.addEventListener('beforeunload', this._beforeUnloadHandler);\n }\n\n /** \uC5F0\uACB0 \uC885\uB8CC. */\n async disconnect() {\n // beforeunload \uD574\uC81C\n if (this._beforeUnloadHandler) {\n window.removeEventListener('beforeunload', this._beforeUnloadHandler);\n this._beforeUnloadHandler = null;\n }\n\n if (this._signaling) {\n this._signaling.sendLeave();\n // Leave \uBA54\uC2DC\uC9C0\uAC00 flush\uB420 \uB54C\uAE4C\uC9C0 \uB300\uAE30 \uD6C4 WS \uB2EB\uAE30\n await new Promise(r => setTimeout(r, 100));\n this._signaling.disconnect();\n }\n this._closePeerConnection();\n this._slots.clear();\n this._connected = false;\n }\n\n /** \uCC44\uD305 \uC804\uC1A1. */\n chat(text) {\n if (this._signaling) {\n this._signaling.sendChat(text, this.nickname);\n }\n }\n\n /** \uD0A4\uD504\uB808\uC784 \uC694\uCCAD. */\n requestKeyframe() {\n if (this._signaling) {\n this._signaling.sendPLI();\n }\n }\n\n /** @returns {Map} \uD604\uC7AC \uC2AC\uB86F \uC815\uBCF4 */\n get slots() {\n return this._slots;\n }\n\n // \u2500\u2500 Internal \u2500\u2500\n\n /** @private */\n _onSignalingOpen() {\n this._signaling.sendJoin(this.peerId, this.channelId, {\n nickname: this.nickname,\n token: this.token,\n mode: this.mode,\n });\n // Join \uC804\uC1A1 \uD6C4 \uBC14\uB85C PeerConnection \uC0DD\uC131\n this._createPeerConnection().catch((e) => this._emit('error', e));\n }\n\n /** @private */\n _onSignalingMessage(msg) {\n switch (msg.type) {\n case SignalType.ANSWER:\n this._handleAnswer(msg);\n break;\n case SignalType.CANDIDATE:\n this._handleCandidate(msg);\n break;\n case SignalType.SLOT_INFO:\n this._handleSlotInfo(msg);\n break;\n case SignalType.CHAT:\n this._emit('chat', {\n peerId: msg.peer_id,\n nickname: msg.nickname,\n text: msg.text,\n });\n break;\n case SignalType.ERROR:\n this._emit('error', new Error(msg.message));\n break;\n }\n }\n\n /** @private */\n _onSignalingClose(evt) {\n this._closePeerConnection();\n this._connected = false;\n this._emit('close', evt);\n }\n\n /** @private */\n async _handleAnswer(msg) {\n if (!this._pc) return;\n try {\n await this._pc.setRemoteDescription(\n new RTCSessionDescription({ type: 'answer', sdp: msg.sdp })\n );\n // Flush queued ICE candidates\n for (const c of this._candidateQueue) {\n await this._pc.addIceCandidate(c);\n }\n this._candidateQueue = [];\n } catch (e) {\n this._emit('error', e);\n }\n }\n\n /** @private */\n async _handleCandidate(msg) {\n const candidate = new RTCIceCandidate({\n candidate: msg.candidate,\n sdpMid: msg.sdp_mid,\n sdpMLineIndex: msg.sdp_m_line_index,\n });\n if (this._pc && this._pc.remoteDescription) {\n try {\n await this._pc.addIceCandidate(candidate);\n } catch (e) {\n console.warn('[FuzionX] ICE candidate error:', e);\n }\n } else {\n this._candidateQueue.push(candidate);\n }\n }\n\n /** @private */\n _handleSlotInfo(msg) {\n const slotIndex = parseInt(msg.stream_id.replace('stream_', ''), 10);\n\n // nickname\uC774 \uBE48 \uBB38\uC790\uC5F4\uC774\uBA74 \uC2AC\uB86F \uD574\uC81C (stream \uCC38\uC870\uB294 \uBCF4\uC874)\n if (!msg.nickname || msg.nickname === '') {\n const existing = this._slots.get(slotIndex);\n console.log('[FuzionX-V] slot_remove:', slotIndex, 'existing stream:', !!existing?.stream);\n if (existing) {\n this._slots.set(slotIndex, { slotIndex, stream: existing.stream });\n }\n this._emit('slot_remove', { slotIndex, senderId: msg.sender_id });\n return;\n }\n\n const existing = this._slots.get(slotIndex) || {};\n console.log('[FuzionX-V] slot assign:', slotIndex, 'nickname:', msg.nickname, 'has stream:', !!existing.stream);\n this._slots.set(slotIndex, {\n ...existing,\n slotIndex,\n streamId: msg.stream_id,\n nickname: msg.nickname,\n senderId: msg.sender_id,\n });\n this._emit('slot', this._slots.get(slotIndex));\n }\n\n /**\n * PeerConnection \uC0DD\uC131 + Non-Trickle ICE Offer.\n * old alloy-player \uAC80\uC99D \uD328\uD134:\n * 1. addTransceiver(recvonly) \u00D7 maxSlots \u2014 \uC11C\uBC84 m-line \uB9E4\uD551\n * 2. createOffer \u2192 H264 SDP \uAC15\uC81C\n * 3. ICE gathering \uC644\uB8CC \uB300\uAE30 \uD6C4 SDP \uC804\uCCB4 \uC804\uC1A1 (Non-Trickle)\n * @private\n */\n async _createPeerConnection() {\n this._pc = new RTCPeerConnection(this.rtcConfig);\n\n // Non-Trickle: ICE candidate\uB294 SDP\uC5D0 \uD3EC\uD568\uB418\uC5B4 \uC804\uC1A1\uB428\n // (onicecandidate\uB294 gathering \uCD94\uC801\uC6A9\uC73C\uB85C\uB9CC \uC720\uC9C0)\n\n // \uC218\uC2E0 \uD2B8\uB799 \uB9E4\uD551\n let videoTrackCount = 0;\n this._pc.ontrack = (event) => {\n const track = event.track;\n\n if (track.kind === 'video') {\n const slotIndex = videoTrackCount++;\n\n let stream = event.streams[0];\n if (!stream) {\n stream = new MediaStream();\n stream.addTrack(track);\n }\n\n const slot = this._slots.get(slotIndex) || { slotIndex };\n slot.stream = stream;\n this._slots.set(slotIndex, slot);\n\n this._emit('stream', stream, slotIndex);\n\n if (!this._connected) {\n this._connected = true;\n this._emit('connected');\n\n // \uC989\uC2DC \uD0A4\uD504\uB808\uC784 \uC694\uCCAD (\uCCAB \uD504\uB808\uC784 \uBE60\uB974\uAC8C)\n if (this._signaling) {\n this._signaling.sendPLI();\n }\n }\n }\n };\n\n this._pc.onconnectionstatechange = () => {\n const state = this._pc?.connectionState;\n if (state === 'failed' || state === 'disconnected') {\n this._emit('error', new Error(`PeerConnection ${state}`));\n }\n };\n\n // recvonly Transceiver \uCD94\uAC00 (\uC11C\uBC84 \uC2AC\uB86F \uC218\uC5D0 \uB9DE\uCDA4)\n for (let i = 0; i < this._maxSlots; i++) {\n this._pc.addTransceiver('video', { direction: 'recvonly' });\n this._pc.addTransceiver('audio', { direction: 'recvonly' });\n }\n\n // Offer \uC0DD\uC131 + H264/Opus \uCF54\uB371 \uAC15\uC81C\n const offer = await this._pc.createOffer();\n offer.sdp = FuzionXViewer._forceCodecs(offer.sdp);\n await this._pc.setLocalDescription(offer);\n\n // Non-Trickle ICE: gathering \uC644\uB8CC \uB300\uAE30 \uD6C4 \uC804\uCCB4 SDP \uC804\uC1A1\n await this._waitForIceGathering();\n\n // gathering \uC644\uB8CC \uD6C4 \uCD5C\uC885 SDP (ICE candidates \uD3EC\uD568) \uC804\uC1A1\n const finalSdp = this._pc.localDescription?.sdp;\n if (finalSdp) {\n this._signaling.sendOffer(finalSdp);\n }\n }\n\n /**\n * ICE gathering \uC644\uB8CC \uB300\uAE30 (Non-Trickle)\n * @private\n */\n _waitForIceGathering() {\n return new Promise((resolve) => {\n if (this._pc.iceGatheringState === 'complete') {\n return resolve();\n }\n const check = () => {\n if (this._pc?.iceGatheringState === 'complete') {\n this._pc.removeEventListener('icegatheringstatechange', check);\n resolve();\n }\n };\n this._pc.addEventListener('icegatheringstatechange', check);\n // 500ms \uD6C4 gathering \uC548 \uB05D\uB098\uBA74 \uD604\uC7AC SDP\uB85C \uC9C4\uD589\n setTimeout(() => {\n if (this._pc) {\n this._pc.removeEventListener('icegatheringstatechange', check);\n }\n resolve();\n }, 150);\n });\n }\n\n /**\n * SDP\uC5D0\uC11C H264 + Opus \uCF54\uB371 \uC6B0\uC120 \uAC15\uC81C.\n * @private\n */\n static _forceCodecs(sdp) {\n let lines = sdp.split('\\r\\n');\n\n // Video: H264 \uC6B0\uC120\n const videoIdx = lines.findIndex((l) => l.startsWith('m=video'));\n if (videoIdx !== -1) {\n const h264Pts = [];\n const h264Scores = new Map();\n lines.forEach((l) => {\n const m = l.match(/a=rtpmap:(\\d+) H264\\/90000/);\n if (m) {\n h264Pts.push(m[1]);\n h264Scores.set(m[1], 0);\n }\n });\n lines.forEach((l) => {\n if (l.startsWith('a=fmtp:')) {\n const pt = l.split(' ')[0].split(':')[1];\n if (h264Scores.has(pt)) {\n if (l.includes('profile-level-id=42e01f')) h264Scores.set(pt, 100);\n else if (l.includes('profile-level-id=42001f')) h264Scores.set(pt, 80);\n if (l.includes('packetization-mode=1')) h264Scores.set(pt, (h264Scores.get(pt) || 0) + 10);\n }\n }\n });\n if (h264Pts.length > 0) {\n h264Pts.sort((a, b) => h264Scores.get(b) - h264Scores.get(a));\n const parts = lines[videoIdx].split(' ');\n const otherPts = parts.slice(3).filter((pt) => !h264Pts.includes(pt));\n lines[videoIdx] = [...parts.slice(0, 3), ...h264Pts, ...otherPts].join(' ');\n }\n }\n\n // Audio: Opus \uC6B0\uC120\n const audioIdx = lines.findIndex((l) => l.startsWith('m=audio'));\n if (audioIdx !== -1) {\n const opusPts = [];\n lines.forEach((l) => {\n const m = l.match(/a=rtpmap:(\\d+) opus\\/48000/);\n if (m) opusPts.push(m[1]);\n });\n if (opusPts.length > 0) {\n const parts = lines[audioIdx].split(' ');\n const otherPts = parts.slice(3).filter((pt) => !opusPts.includes(pt));\n lines[audioIdx] = [...parts.slice(0, 3), ...opusPts, ...otherPts].join(' ');\n }\n }\n\n return lines.join('\\r\\n');\n }\n\n /** @private */\n _closePeerConnection() {\n if (this._pc) {\n this._pc.close();\n this._pc = null;\n }\n this._candidateQueue = [];\n }\n\n}\n", "/**\n * @fuzionx/player \u2014 FuzionXPublisher (\uC1A1\uCD9C\uC790 \uBAA8\uB4DC)\n *\n * \uB85C\uCEEC \uCE74\uBA54\uB77C/\uB9C8\uC774\uD06C \u2192 \uC11C\uBC84 \uC804\uC1A1.\n * 2\uAC00\uC9C0 \uBC29\uC2DD \uC9C0\uC6D0:\n * A. WebSocket \uBC29\uC2DD (videochat \uC591\uBC29\uD5A5)\n * B. WHIP \uBC29\uC2DD (OBS/\uB2E8\uBC29\uD5A5 \uBC29\uC1A1)\n *\n * @example WebSocket\n * const pub = new FuzionXPublisher({\n * url: 'wss://media:50002',\n * channelId: 'my-live',\n * mode: 'videochat',\n * nickname: '\uBC1C\uD45C\uC790',\n * });\n * pub.on('ready', () => console.log('Publishing!'));\n * pub.connect();\n *\n * @example WHIP\n * const pub = new FuzionXPublisher({\n * whipUrl: 'https://media:7777/whip/my-live',\n * });\n * pub.connect();\n */\n\nimport { FuzionXSignaling } from './FuzionXSignaling.js';\nimport { DEFAULT_ICE_SERVERS, SignalType, SessionMode, MAX_SLOTS } from './constants.js';\n\nexport class FuzionXPublisher {\n /**\n * @param {Object} opts\n * @param {string} [opts.url] - WebSocket URL (WS \uBC29\uC2DD)\n * @param {string} [opts.whipUrl] - WHIP URL (WHIP \uBC29\uC2DD)\n * @param {string} [opts.channelId] - \uCC44\uB110 ID (WS \uBC29\uC2DD \uD544\uC218)\n * @param {string} [opts.mode='broadcast'] - 'broadcast' | 'videochat'\n * @param {string} [opts.nickname] - \uB2C9\uB124\uC784\n * @param {string} [opts.token] - \uC778\uC99D \uD1A0\uD070\n * @param {string} [opts.peerId] - \uD53C\uC5B4 ID\n * @param {MediaStreamConstraints} [opts.media] - getUserMedia \uC81C\uC57D\n * @param {MediaStream} [opts.stream] - \uC774\uBBF8 \uD68D\uB4DD\uD55C MediaStream\n * @param {RTCConfiguration} [opts.rtcConfig] - WebRTC \uC124\uC815\n * @param {boolean} [opts.autoReconnect=true]\n */\n constructor(opts) {\n // WHIP or WebSocket\n this.whipUrl = opts.whipUrl || null;\n this.url = opts.url || null;\n this.hubUrl = opts.hubUrl || null;\n this.channelId = opts.channelId || null;\n this.mode = opts.mode || SessionMode.BROADCAST;\n this.nickname = opts.nickname || null;\n this.token = opts.token || null;\n this.peerId = opts.peerId || `pub-${Math.random().toString(36).slice(2, 10)}`;\n this.autoReconnect = opts.autoReconnect !== false;\n\n this.mediaConstraints = opts.media || { video: true, audio: true };\n this._externalStream = opts.stream || null;\n\n this.rtcConfig = opts.rtcConfig || {\n iceServers: DEFAULT_ICE_SERVERS,\n bundlePolicy: 'max-bundle',\n rtcpMuxPolicy: 'require',\n };\n\n /** @private */\n this._signaling = null;\n this._pc = null;\n this._localStream = null;\n this._listeners = {};\n this._candidateQueue = [];\n this._whipResourceUrl = null;\n this._maxSlots = MAX_SLOTS[this.mode] || 1;\n this._slots = new Map();\n this._connected = false;\n }\n\n // \u2500\u2500 Event System \u2500\u2500\n\n on(event, handler) {\n if (!this._listeners[event]) this._listeners[event] = [];\n this._listeners[event].push(handler);\n return this;\n }\n\n /** @private */\n _emit(event, ...args) {\n (this._listeners[event] || []).forEach((fn) => fn(...args));\n }\n\n // \u2500\u2500 Lifecycle \u2500\u2500\n\n /** \uC5F0\uACB0 \uC2DC\uC791 (\uBBF8\uB514\uC5B4 \uD68D\uB4DD \u2192 WebSocket \uB610\uB294 WHIP). */\n async connect() {\n try {\n // Hub \uB77C\uC6B0\uD305: hubUrl\uC774 \uC788\uC73C\uBA74 Hub\uC5D0\uC11C \uCC44\uB110 \uC0DD\uC131 (Origin \uD560\uB2F9)\n if (!this.url && !this.whipUrl && this.hubUrl && this.channelId) {\n // Publisher\uB294 POST \uC6B0\uC120: \uCC44\uB110 \uC0DD\uC131 \u2192 Origin ws_url \uBC18\uD658\n // GET\uC740 Viewer\uC6A9 (Edge \uBD84\uC0B0 \uD2B8\uB9AC\uAC70) \u2192 Publisher\uAC00 \uD638\uCD9C\uD558\uBA74 \uC548 \uB428\n const createRes = await fetch(`${this.hubUrl}/api/channels`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n channel_id: this.channelId,\n source_type: 'webrtc',\n }),\n });\n\n let data;\n if (createRes.status === 409) {\n // \uD654\uC0C1\uCC44\uD305: \uC774\uBBF8 \uC874\uC7AC\uD558\uB294 \uCC44\uB110\uC5D0 \uCD94\uAC00 Publisher \uC785\uC7A5\n // GET\uC73C\uB85C \uAE30\uC874 \uCC44\uB110 \uC815\uBCF4 \uC870\uD68C\n const getRes = await fetch(`${this.hubUrl}/api/channels/${this.channelId}`);\n if (!getRes.ok) throw new Error(`Channel not found: ${this.channelId}`);\n data = await getRes.json();\n } else if (!createRes.ok) {\n throw new Error(`Failed to create channel: ${this.channelId}`);\n } else {\n data = await createRes.json();\n }\n\n if (data.ws_url) {\n const isSecure = this.hubUrl.startsWith('https');\n this.url = data.ws_url.replace(/^ws(s?):/, isSecure ? 'wss:' : 'ws:');\n } else {\n const isSecure = this.hubUrl.startsWith('https');\n const wsProto = isSecure ? 'wss' : 'ws';\n this.url = `${wsProto}://${data.media_ip}:${data.webrtc_port}`;\n }\n }\n\n await this._acquireMedia();\n\n if (this.whipUrl) {\n await this._connectWhip();\n } else if (this.url) {\n this._connectWebSocket();\n } else {\n throw new Error('url, hubUrl, \uB610\uB294 whipUrl \uC911 \uD558\uB098\uB97C \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4.');\n }\n\n // \uD398\uC774\uC9C0 \uC774\uB3D9/\uD0ED \uB2EB\uAE30 \uC2DC \uC880\uBE44 \uBC29\uC9C0\n this._beforeUnloadHandler = () => {\n if (this._signaling && this._signaling.connected) {\n this._signaling.sendLeave();\n }\n this._closePeerConnection();\n this._stopMedia();\n };\n window.addEventListener('beforeunload', this._beforeUnloadHandler);\n } catch (e) {\n this._emit('error', e);\n }\n }\n\n /** \uC5F0\uACB0 \uC885\uB8CC. */\n async disconnect() {\n // beforeunload \uD574\uC81C\n if (this._beforeUnloadHandler) {\n window.removeEventListener('beforeunload', this._beforeUnloadHandler);\n this._beforeUnloadHandler = null;\n }\n\n if (this.whipUrl && this._whipResourceUrl) {\n // WHIP DELETE\n try {\n const baseUrl = new URL(this.whipUrl).origin;\n await fetch(`${baseUrl}${this._whipResourceUrl}`, { method: 'DELETE' });\n } catch (e) {\n console.warn('[FuzionX] WHIP DELETE error:', e);\n }\n this._whipResourceUrl = null;\n }\n\n if (this._signaling) {\n this._signaling.sendLeave();\n // Leave \uBA54\uC2DC\uC9C0\uAC00 flush\uB420 \uB54C\uAE4C\uC9C0 \uB300\uAE30 \uD6C4 WS \uB2EB\uAE30\n await new Promise(r => setTimeout(r, 100));\n this._signaling.disconnect();\n }\n\n this._closePeerConnection();\n this._stopMedia();\n this._connected = false;\n }\n\n /** \uCC44\uD305 \uC804\uC1A1. */\n chat(text) {\n if (this._signaling) {\n this._signaling.sendChat(text, this.nickname);\n }\n }\n\n /** @returns {MediaStream|null} \uB85C\uCEEC \uC2A4\uD2B8\uB9BC */\n get localStream() {\n return this._localStream;\n }\n\n /** @returns {Map} \uC2AC\uB86F \uC815\uBCF4 (videochat: \uB2E4\uB978 \uCC38\uAC00\uC790) */\n get slots() {\n return this._slots;\n }\n\n // \u2500\u2500 Media \u2500\u2500\n\n /** @private \uBBF8\uB514\uC5B4 \uD68D\uB4DD */\n async _acquireMedia() {\n if (this._externalStream) {\n this._localStream = this._externalStream;\n } else {\n this._localStream = await navigator.mediaDevices.getUserMedia(this.mediaConstraints);\n }\n this._emit('media', this._localStream);\n }\n\n /** @private */\n _stopMedia() {\n if (this._localStream && !this._externalStream) {\n this._localStream.getTracks().forEach((t) => t.stop());\n }\n this._localStream = null;\n }\n\n // \u2500\u2500 WHIP \u2500\u2500\n\n /** @private WHIP \uC5F0\uACB0 */\n async _connectWhip() {\n this._pc = new RTCPeerConnection(this.rtcConfig);\n\n // \uD2B8\uB799 \uCD94\uAC00\n this._localStream.getTracks().forEach((track) => {\n this._pc.addTrack(track, this._localStream);\n });\n\n // ICE Gathering \uC644\uB8CC \uB300\uAE30\n const offer = await this._pc.createOffer();\n offer.sdp = FuzionXPublisher._forceCodecs(offer.sdp);\n await this._pc.setLocalDescription(offer);\n\n // Gathering \uC644\uB8CC \uB300\uAE30\n await new Promise((resolve) => {\n if (this._pc.iceGatheringState === 'complete') {\n resolve();\n } else {\n this._pc.onicegatheringstatechange = () => {\n if (this._pc.iceGatheringState === 'complete') resolve();\n };\n // \uC548\uC804 \uD0C0\uC784\uC544\uC6C3\n setTimeout(resolve, 150);\n }\n });\n\n const localDesc = this._pc.localDescription;\n // Hub API\uC758 whip_url\uC740 \uC774\uBBF8 ?token=xxx \uD3EC\uD568 \uAC00\uB2A5\n let url = this.whipUrl;\n if (this.token && !url.includes('token=')) {\n url += (url.includes('?') ? '&' : '?') + `token=${this.token}`;\n }\n\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/sdp' },\n body: localDesc.sdp,\n });\n\n if (response.status !== 201) {\n throw new Error(`WHIP failed: ${response.status} ${await response.text()}`);\n }\n\n const answerSdp = await response.text();\n this._whipResourceUrl = response.headers.get('location');\n\n await this._pc.setRemoteDescription(\n new RTCSessionDescription({ type: 'answer', sdp: answerSdp })\n );\n\n this._pc.onconnectionstatechange = () => {\n const state = this._pc?.connectionState;\n if (state === 'connected') {\n this._connected = true;\n this._emit('ready');\n } else if (state === 'failed' || state === 'disconnected') {\n this._emit('error', new Error(`WHIP PeerConnection ${state}`));\n }\n };\n\n this._emit('ready');\n }\n\n // \u2500\u2500 WebSocket \u2500\u2500\n\n /** @private WS \uC5F0\uACB0 */\n _connectWebSocket() {\n this._signaling = new FuzionXSignaling({\n url: this.url,\n autoReconnect: this.autoReconnect,\n onOpen: () => this._onSignalingOpen(),\n onMessage: (msg) => this._onSignalingMessage(msg),\n onClose: (evt) => {\n this._closePeerConnection();\n this._connected = false;\n this._emit('close', evt);\n },\n onError: (err) => this._emit('error', err),\n });\n this._signaling.connect();\n }\n\n /** @private */\n async _onSignalingOpen() {\n this._signaling.sendJoin(this.peerId, this.channelId, {\n nickname: this.nickname,\n token: this.token,\n mode: this.mode,\n });\n await this._createPeerConnection();\n }\n\n /** @private */\n async _createPeerConnection() {\n this._pc = new RTCPeerConnection(this.rtcConfig);\n\n // Non-Trickle: ICE candidate\uB294 SDP\uC5D0 \uD3EC\uD568\n\n // \uB85C\uCEEC \uD2B8\uB799 \uCD94\uAC00 (\uC1A1\uCD9C)\n this._localStream.getTracks().forEach((track) => {\n this._pc.addTrack(track, this._localStream);\n });\n\n // videochat \uBAA8\uB4DC: \uC218\uC2E0 \uD2B8\uB799\uB3C4 \uC900\uBE44 (\uC591\uBC29\uD5A5)\n if (this.mode === SessionMode.VIDEOCHAT) {\n for (let i = 0; i < this._maxSlots; i++) {\n this._pc.addTransceiver('video', { direction: 'recvonly' });\n this._pc.addTransceiver('audio', { direction: 'recvonly' });\n }\n\n // \uC1A1\uCD9C \uD2B8\uB799\uC740 addTrack\uC73C\uB85C \uCD94\uAC00\uB428 \u2192 transceiver\uBC29\uD5A5\uC744 sendrecv\uB85C \uC5C5\uADF8\uB808\uC774\uB4DC\n const transceivers = this._pc.getTransceivers();\n transceivers.forEach((t) => {\n if (t.sender.track && t.direction === 'recvonly') {\n t.direction = 'sendrecv';\n }\n });\n\n let videoTrackCount = 0;\n this._pc.ontrack = (event) => {\n const track = event.track;\n if (track.kind === 'video') {\n const slotIndex = videoTrackCount++;\n let stream = event.streams[0];\n if (!stream) {\n stream = new MediaStream();\n stream.addTrack(track);\n }\n const slot = this._slots.get(slotIndex) || { slotIndex };\n slot.stream = stream;\n this._slots.set(slotIndex, slot);\n this._emit('stream', stream, slotIndex);\n }\n };\n }\n\n this._pc.onconnectionstatechange = () => {\n const state = this._pc?.connectionState;\n if (state === 'connected' && !this._connected) {\n this._connected = true;\n this._emit('ready');\n } else if (state === 'failed' || state === 'disconnected') {\n this._emit('error', new Error(`PeerConnection ${state}`));\n }\n };\n\n // Offer + H264/Opus SDP \uAC15\uC81C\n const offer = await this._pc.createOffer();\n offer.sdp = FuzionXPublisher._forceCodecs(offer.sdp);\n await this._pc.setLocalDescription(offer);\n\n // Non-Trickle: ICE gathering \uC644\uB8CC \uB300\uAE30\n await this._waitForIceGathering();\n\n const finalSdp = this._pc.localDescription?.sdp;\n if (finalSdp) {\n this._signaling.sendOffer(finalSdp);\n }\n }\n\n /** @private */\n _waitForIceGathering() {\n return new Promise((resolve) => {\n if (this._pc.iceGatheringState === 'complete') {\n return resolve();\n }\n const check = () => {\n if (this._pc?.iceGatheringState === 'complete') {\n this._pc.removeEventListener('icegatheringstatechange', check);\n resolve();\n }\n };\n this._pc.addEventListener('icegatheringstatechange', check);\n setTimeout(() => {\n if (this._pc) {\n this._pc.removeEventListener('icegatheringstatechange', check);\n }\n resolve();\n }, 150);\n });\n }\n\n /** @private */\n static _forceCodecs(sdp) {\n let lines = sdp.split('\\r\\n');\n const videoIdx = lines.findIndex((l) => l.startsWith('m=video'));\n if (videoIdx !== -1) {\n const h264Pts = [];\n const h264Scores = new Map();\n lines.forEach((l) => {\n const m = l.match(/a=rtpmap:(\\d+) H264\\/90000/);\n if (m) { h264Pts.push(m[1]); h264Scores.set(m[1], 0); }\n });\n lines.forEach((l) => {\n if (l.startsWith('a=fmtp:')) {\n const pt = l.split(' ')[0].split(':')[1];\n if (h264Scores.has(pt)) {\n if (l.includes('profile-level-id=42e01f')) h264Scores.set(pt, 100);\n else if (l.includes('profile-level-id=42001f')) h264Scores.set(pt, 80);\n if (l.includes('packetization-mode=1')) h264Scores.set(pt, (h264Scores.get(pt) || 0) + 10);\n }\n }\n });\n if (h264Pts.length > 0) {\n h264Pts.sort((a, b) => h264Scores.get(b) - h264Scores.get(a));\n const parts = lines[videoIdx].split(' ');\n const otherPts = parts.slice(3).filter((pt) => !h264Pts.includes(pt));\n lines[videoIdx] = [...parts.slice(0, 3), ...h264Pts, ...otherPts].join(' ');\n }\n }\n const audioIdx = lines.findIndex((l) => l.startsWith('m=audio'));\n if (audioIdx !== -1) {\n const opusPts = [];\n lines.forEach((l) => {\n const m = l.match(/a=rtpmap:(\\d+) opus\\/48000/);\n if (m) opusPts.push(m[1]);\n });\n if (opusPts.length > 0) {\n const parts = lines[audioIdx].split(' ');\n const otherPts = parts.slice(3).filter((pt) => !opusPts.includes(pt));\n lines[audioIdx] = [...parts.slice(0, 3), ...opusPts, ...otherPts].join(' ');\n }\n }\n return lines.join('\\r\\n');\n }\n\n /** @private */\n _onSignalingMessage(msg) {\n switch (msg.type) {\n case SignalType.ANSWER:\n this._handleAnswer(msg);\n break;\n case SignalType.CANDIDATE:\n this._handleCandidate(msg);\n break;\n case SignalType.SLOT_INFO:\n this._handleSlotInfo(msg);\n break;\n case SignalType.CHAT:\n this._emit('chat', {\n peerId: msg.peer_id,\n nickname: msg.nickname,\n text: msg.text,\n });\n break;\n case SignalType.ERROR:\n this._emit('error', new Error(msg.message));\n break;\n }\n }\n\n /** @private */\n async _handleAnswer(msg) {\n if (!this._pc) return;\n try {\n await this._pc.setRemoteDescription(\n new RTCSessionDescription({ type: 'answer', sdp: msg.sdp })\n );\n for (const c of this._candidateQueue) {\n await this._pc.addIceCandidate(c);\n }\n this._candidateQueue = [];\n } catch (e) {\n this._emit('error', e);\n }\n }\n\n /** @private */\n async _handleCandidate(msg) {\n const candidate = new RTCIceCandidate({\n candidate: msg.candidate,\n sdpMid: msg.sdp_mid,\n sdpMLineIndex: msg.sdp_m_line_index,\n });\n if (this._pc && this._pc.remoteDescription) {\n try { await this._pc.addIceCandidate(candidate); } catch (e) { /* ignore */ }\n } else {\n this._candidateQueue.push(candidate);\n }\n }\n\n /** @private */\n _handleSlotInfo(msg) {\n // \uC790\uAE30 \uC790\uC2E0\uC758 SlotInfo\uB294 \uBB34\uC2DC (\uC11C\uBC84\uB294 RTP\uB9CC self-skip, SlotInfo\uB294 \uBCF4\uB0C4)\n if (msg.sender_id === this.peerId) return;\n\n const slotIndex = parseInt(msg.stream_id.replace('stream_', ''), 10);\n if (!msg.nickname || msg.nickname === '') {\n // stream \uCC38\uC870\uB294 \uBCF4\uC874 (WebRTC \uD2B8\uB799\uC740 PC \uC218\uBA85\uACFC \uB3D9\uC77C)\n const existing = this._slots.get(slotIndex);\n console.log('[FuzionX] slot_remove:', slotIndex, 'existing stream:', !!existing?.stream);\n if (existing) {\n this._slots.set(slotIndex, { slotIndex, stream: existing.stream });\n }\n this._emit('slot_remove', { slotIndex, senderId: msg.sender_id });\n return;\n }\n const existing = this._slots.get(slotIndex) || {};\n console.log('[FuzionX] slot assign:', slotIndex, 'nickname:', msg.nickname, 'has stream:', !!existing.stream);\n this._slots.set(slotIndex, {\n ...existing,\n slotIndex,\n streamId: msg.stream_id,\n nickname: msg.nickname,\n senderId: msg.sender_id,\n });\n this._emit('slot', this._slots.get(slotIndex));\n }\n\n /** @private */\n _closePeerConnection() {\n if (this._pc) {\n this._pc.close();\n this._pc = null;\n }\n this._candidateQueue = [];\n }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["/**\n * @fuzionx/player \u2014 Constants & Default Configuration\n */\n\n/** Default ICE Servers */\nexport const DEFAULT_ICE_SERVERS = [\n { urls: 'stun:stun.l.google.com:19302' },\n { urls: 'stun:stun1.l.google.com:19302' },\n];\n\n/** Signaling message types (maps to server SignalMessage enum) */\nexport const SignalType = {\n JOIN: 'join',\n OFFER: 'offer',\n ANSWER: 'answer',\n CANDIDATE: 'candidate',\n PLI: 'pli',\n LEAVE: 'leave',\n SLOT_INFO: 'slot_info',\n CHAT: 'chat',\n ERROR: 'error',\n};\n\n/** Session modes */\nexport const SessionMode = {\n BROADCAST: 'broadcast',\n VIDEOCHAT: 'videochat',\n};\n\n/** Max slots per mode */\nexport const MAX_SLOTS = {\n [SessionMode.BROADCAST]: 1,\n [SessionMode.VIDEOCHAT]: 9,\n};\n\n/** Default reconnect settings */\nexport const RECONNECT = {\n MAX_RETRIES: 5,\n BASE_DELAY_MS: 1000,\n MAX_DELAY_MS: 30000,\n};\n\n/** Codec preferences */\nexport const CODEC = {\n VIDEO_MIME: 'video/H264',\n AUDIO_MIME: 'audio/opus',\n VIDEO_CLOCK: 90000,\n AUDIO_CLOCK: 48000,\n};\n", "/**\n * @fuzionx/player \u2014 WebSocket Signaling Layer\n *\n * FuzionX Media Server\uC758 JSON \uC2DC\uADF8\uB110\uB9C1 \uD504\uB85C\uD1A0\uCF5C \uCEA1\uC290\uD654.\n * \uC790\uB3D9 \uC7AC\uC5F0\uACB0 + \uC774\uBCA4\uD2B8 \uC2DC\uC2A4\uD15C.\n */\n\nimport { SignalType, RECONNECT } from './constants.js';\n\nexport class FuzionXSignaling {\n /**\n * @param {Object} opts\n * @param {string} opts.url - WebSocket URL (ws:// or wss://)\n * @param {Function} [opts.onMessage] - \uBA54\uC2DC\uC9C0 \uC218\uC2E0 \uCF5C\uBC31\n * @param {Function} [opts.onOpen] - \uC5F0\uACB0 \uC131\uACF5 \uCF5C\uBC31\n * @param {Function} [opts.onClose] - \uC5F0\uACB0 \uC885\uB8CC \uCF5C\uBC31\n * @param {Function} [opts.onError] - \uC5D0\uB7EC \uCF5C\uBC31\n * @param {boolean} [opts.autoReconnect=true]\n */\n constructor(opts) {\n this.url = opts.url;\n this.onMessage = opts.onMessage || (() => {});\n this.onOpen = opts.onOpen || (() => {});\n this.onClose = opts.onClose || (() => {});\n this.onError = opts.onError || (() => {});\n this.autoReconnect = opts.autoReconnect !== false;\n\n /** @private */\n this._ws = null;\n this._retryCount = 0;\n this._reconnectTimer = null;\n this._intentionalClose = false;\n }\n\n /** WebSocket \uC5F0\uACB0. */\n connect() {\n this._intentionalClose = false;\n this._doConnect();\n }\n\n /** @private */\n _doConnect() {\n try {\n this._ws = new WebSocket(this.url);\n } catch (e) {\n this.onError(e);\n this._scheduleReconnect();\n return;\n }\n\n this._ws.onopen = () => {\n this._retryCount = 0;\n this.onOpen();\n };\n\n this._ws.onmessage = (event) => {\n try {\n const msg = JSON.parse(event.data);\n this.onMessage(msg);\n } catch (e) {\n console.warn('[FuzionX] Invalid JSON:', event.data);\n }\n };\n\n this._ws.onclose = (event) => {\n this.onClose(event);\n if (!this._intentionalClose && this.autoReconnect) {\n this._scheduleReconnect();\n }\n };\n\n this._ws.onerror = (event) => {\n this.onError(event);\n };\n }\n\n /** JSON \uBA54\uC2DC\uC9C0 \uC804\uC1A1. */\n send(msg) {\n if (this._ws && this._ws.readyState === WebSocket.OPEN) {\n this._ws.send(JSON.stringify(msg));\n return true;\n }\n return false;\n }\n\n // \u2500\u2500 \uC2DC\uADF8\uB110\uB9C1 \uD5EC\uD37C \u2500\u2500\n\n /** Join \uC804\uC1A1 */\n sendJoin(peerId, channelId, opts = {}) {\n return this.send({\n type: SignalType.JOIN,\n peer_id: peerId,\n channel_id: channelId,\n nickname: opts.nickname || null,\n token: opts.token || null,\n mode: opts.mode || null,\n });\n }\n\n /** SDP Offer \uC804\uC1A1 */\n sendOffer(sdp) {\n return this.send({ type: SignalType.OFFER, sdp });\n }\n\n /** SDP Answer \uC804\uC1A1 */\n sendAnswer(sdp) {\n return this.send({ type: SignalType.ANSWER, sdp });\n }\n\n /** ICE Candidate \uC804\uC1A1 */\n sendCandidate(candidate) {\n return this.send({\n type: SignalType.CANDIDATE,\n candidate: candidate.candidate,\n sdp_mid: candidate.sdpMid,\n sdp_m_line_index: candidate.sdpMLineIndex,\n });\n }\n\n /** Chat \uC804\uC1A1 */\n sendChat(text, nickname) {\n return this.send({\n type: SignalType.CHAT,\n text,\n nickname: nickname || null,\n peer_id: null,\n });\n }\n\n /** PLI \uC694\uCCAD */\n sendPLI() {\n return this.send({ type: SignalType.PLI });\n }\n\n /** Leave \uC804\uC1A1 */\n sendLeave() {\n return this.send({ type: SignalType.LEAVE });\n }\n\n /** \uC5F0\uACB0 \uC885\uB8CC (\uC7AC\uC5F0\uACB0 \uC548 \uD568). */\n disconnect() {\n this._intentionalClose = true;\n clearTimeout(this._reconnectTimer);\n if (this._ws) {\n this._ws.close();\n this._ws = null;\n }\n }\n\n /** @returns {boolean} \uC5F0\uACB0 \uC0C1\uD0DC */\n get connected() {\n return this._ws && this._ws.readyState === WebSocket.OPEN;\n }\n\n /** @private \uC7AC\uC5F0\uACB0 \uC2A4\uCF00\uC904 (Exponential Backoff). */\n _scheduleReconnect() {\n if (this._retryCount >= RECONNECT.MAX_RETRIES) {\n console.error('[FuzionX] Max reconnect retries reached.');\n this.onError(new Error('Max reconnect retries'));\n return;\n }\n const delay = Math.min(\n RECONNECT.BASE_DELAY_MS * Math.pow(2, this._retryCount),\n RECONNECT.MAX_DELAY_MS\n );\n this._retryCount++;\n console.log(`[FuzionX] Reconnecting in ${delay}ms (${this._retryCount}/${RECONNECT.MAX_RETRIES})`);\n this._reconnectTimer = setTimeout(() => this._doConnect(), delay);\n }\n}\n", "/**\n * @fuzionx/player \u2014 FuzionXViewer (\uC218\uC2E0\uC790 \uBAA8\uB4DC)\n *\n * \uC11C\uBC84\uC5D0\uC11C MediaStream\uC744 \uC218\uC2E0\uD558\uC5EC <video> \uC5D8\uB9AC\uBA3C\uD2B8\uC5D0 \uB80C\uB354\uB9C1.\n * broadcast(1 stream) / videochat(\uCD5C\uB300 9 streams) \uC9C0\uC6D0.\n *\n * @example\n * const viewer = new FuzionXViewer({\n * url: 'wss://media:50002',\n * channelId: 'my-live',\n * mode: 'broadcast',\n * });\n * viewer.on('stream', (stream, slotIndex) => {\n * videoEl.srcObject = stream;\n * });\n * viewer.connect();\n */\n\nimport { FuzionXSignaling } from './FuzionXSignaling.js';\nimport { DEFAULT_ICE_SERVERS, SignalType, SessionMode, MAX_SLOTS } from './constants.js';\n\nexport class FuzionXViewer {\n /**\n * @param {Object} opts\n * @param {string} [opts.url] - WebSocket URL (\uC9C1\uC811 \uC9C0\uC815)\n * @param {string} [opts.hubUrl] - Hub API URL (\uC790\uB3D9 \uB77C\uC6B0\uD305: Hub\uC5D0\uC11C \uBBF8\uB514\uC5B4 \uC11C\uBC84 \uC870\uD68C)\n * @param {string} opts.channelId - \uCC44\uB110 ID\n * @param {string} [opts.mode='broadcast'] - 'broadcast' | 'videochat'\n * @param {string} [opts.nickname] - \uB2C9\uB124\uC784\n * @param {string} [opts.token] - \uC778\uC99D \uD1A0\uD070\n * @param {string} [opts.peerId] - \uD53C\uC5B4 ID (\uC790\uB3D9 \uC0DD\uC131)\n * @param {RTCConfiguration} [opts.rtcConfig] - WebRTC \uC124\uC815 \uC624\uBC84\uB77C\uC774\uB4DC\n * @param {boolean} [opts.autoReconnect=true]\n */\n constructor(opts) {\n this.url = opts.url || null;\n this.hubUrl = opts.hubUrl || null;\n this.channelId = opts.channelId;\n this.mode = opts.mode || SessionMode.BROADCAST;\n this.nickname = opts.nickname || null;\n this.token = opts.token || null;\n this.peerId = opts.peerId || `viewer-${Math.random().toString(36).slice(2, 10)}`;\n this.autoReconnect = opts.autoReconnect !== false;\n\n this.rtcConfig = opts.rtcConfig || {\n iceServers: DEFAULT_ICE_SERVERS,\n bundlePolicy: 'max-bundle',\n rtcpMuxPolicy: 'require',\n };\n\n /** @private */\n this._signaling = null;\n this._pc = null;\n this._listeners = {};\n this._slots = new Map(); // slotIndex \u2192 { streamId, nickname, senderId, stream }\n this._maxSlots = MAX_SLOTS[this.mode] || 1;\n this._candidateQueue = [];\n this._connected = false;\n }\n\n // \u2500\u2500 Event System \u2500\u2500\n\n /**\n * \uC774\uBCA4\uD2B8 \uB9AC\uC2A4\uB108 \uB4F1\uB85D.\n * @param {'stream'|'slot'|'slot_remove'|'chat'|'error'|'close'|'connected'} event\n * @param {Function} handler\n */\n on(event, handler) {\n if (!this._listeners[event]) this._listeners[event] = [];\n this._listeners[event].push(handler);\n return this;\n }\n\n /** @private */\n _emit(event, ...args) {\n (this._listeners[event] || []).forEach((fn) => fn(...args));\n }\n\n // \u2500\u2500 Lifecycle \u2500\u2500\n\n /** \uC11C\uBC84 \uC5F0\uACB0 + WebRTC \uC138\uC158 \uC2DC\uC791. */\n async connect() {\n // Hub \uB77C\uC6B0\uD305: hubUrl\uC774 \uC788\uC73C\uBA74 Hub\uC5D0\uC11C \uBBF8\uB514\uC5B4 \uC11C\uBC84 \uC870\uD68C\n if (!this.url && this.hubUrl) {\n try {\n const res = await fetch(`${this.hubUrl}/api/channels/${this.channelId}`);\n if (!res.ok) throw new Error(`Channel not found: ${this.channelId}`);\n const data = await res.json();\n // ws_url \uC0AC\uC6A9 \uB610\uB294 ip:port\uC5D0\uC11C \uC0DD\uC131\n if (data.ws_url) {\n // Hub\uC640 \uAC19\uC740 \uD504\uB85C\uD1A0\uCF5C \uC0AC\uC6A9 (https\u2192wss, http\u2192ws)\n const isSecure = this.hubUrl.startsWith('https');\n this.url = data.ws_url.replace(/^ws(s?):/, isSecure ? 'wss:' : 'ws:');\n } else {\n const isSecure = this.hubUrl.startsWith('https');\n const wsProto = isSecure ? 'wss' : 'ws';\n this.url = `${wsProto}://${data.media_ip}:${data.webrtc_port}`;\n }\n } catch (e) {\n this._emit('error', e);\n return;\n }\n }\n\n if (!this.url) {\n this._emit('error', new Error('url \uB610\uB294 hubUrl\uC744 \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4.'));\n return;\n }\n\n this._signaling = new FuzionXSignaling({\n url: this.url,\n autoReconnect: this.autoReconnect,\n onOpen: () => this._onSignalingOpen(),\n onMessage: (msg) => this._onSignalingMessage(msg),\n onClose: (evt) => this._onSignalingClose(evt),\n onError: (err) => this._emit('error', err),\n });\n this._signaling.connect();\n\n // \uD398\uC774\uC9C0 \uC774\uB3D9/\uD0ED \uB2EB\uAE30 \uC2DC \uC880\uBE44 \uBC29\uC9C0\n this._beforeUnloadHandler = () => {\n if (this._signaling && this._signaling.connected) {\n this._signaling.sendLeave();\n }\n this._closePeerConnection();\n };\n window.addEventListener('beforeunload', this._beforeUnloadHandler);\n }\n\n /** \uC5F0\uACB0 \uC885\uB8CC. */\n async disconnect() {\n // beforeunload \uD574\uC81C\n if (this._beforeUnloadHandler) {\n window.removeEventListener('beforeunload', this._beforeUnloadHandler);\n this._beforeUnloadHandler = null;\n }\n\n if (this._signaling) {\n this._signaling.sendLeave();\n // Leave \uBA54\uC2DC\uC9C0\uAC00 flush\uB420 \uB54C\uAE4C\uC9C0 \uB300\uAE30 \uD6C4 WS \uB2EB\uAE30\n await new Promise(r => setTimeout(r, 100));\n this._signaling.disconnect();\n }\n this._closePeerConnection();\n this._slots.clear();\n this._connected = false;\n }\n\n /** \uCC44\uD305 \uC804\uC1A1. */\n chat(text) {\n if (this._signaling) {\n this._signaling.sendChat(text, this.nickname);\n }\n }\n\n /** \uD0A4\uD504\uB808\uC784 \uC694\uCCAD. */\n requestKeyframe() {\n if (this._signaling) {\n this._signaling.sendPLI();\n }\n }\n\n /** @returns {Map} \uD604\uC7AC \uC2AC\uB86F \uC815\uBCF4 */\n get slots() {\n return this._slots;\n }\n\n // \u2500\u2500 Internal \u2500\u2500\n\n /** @private */\n _onSignalingOpen() {\n this._signaling.sendJoin(this.peerId, this.channelId, {\n nickname: this.nickname,\n token: this.token,\n mode: this.mode,\n });\n // Join \uC804\uC1A1 \uD6C4 \uBC14\uB85C PeerConnection \uC0DD\uC131\n this._createPeerConnection().catch((e) => this._emit('error', e));\n }\n\n /** @private */\n _onSignalingMessage(msg) {\n switch (msg.type) {\n case SignalType.ANSWER:\n this._handleAnswer(msg);\n break;\n case SignalType.CANDIDATE:\n this._handleCandidate(msg);\n break;\n case SignalType.SLOT_INFO:\n this._handleSlotInfo(msg);\n break;\n case SignalType.CHAT:\n this._emit('chat', {\n peerId: msg.peer_id,\n nickname: msg.nickname,\n text: msg.text,\n });\n break;\n case SignalType.ERROR:\n this._emit('error', new Error(msg.message));\n break;\n }\n }\n\n /** @private */\n _onSignalingClose(evt) {\n this._closePeerConnection();\n this._connected = false;\n this._emit('close', evt);\n }\n\n /** @private */\n async _handleAnswer(msg) {\n if (!this._pc) return;\n try {\n await this._pc.setRemoteDescription(\n new RTCSessionDescription({ type: 'answer', sdp: msg.sdp })\n );\n // Flush queued ICE candidates\n for (const c of this._candidateQueue) {\n await this._pc.addIceCandidate(c);\n }\n this._candidateQueue = [];\n } catch (e) {\n this._emit('error', e);\n }\n }\n\n /** @private */\n async _handleCandidate(msg) {\n const candidate = new RTCIceCandidate({\n candidate: msg.candidate,\n sdpMid: msg.sdp_mid,\n sdpMLineIndex: msg.sdp_m_line_index,\n });\n if (this._pc && this._pc.remoteDescription) {\n try {\n await this._pc.addIceCandidate(candidate);\n } catch (e) {\n console.warn('[FuzionX] ICE candidate error:', e);\n }\n } else {\n this._candidateQueue.push(candidate);\n }\n }\n\n /** @private */\n _handleSlotInfo(msg) {\n const slotIndex = parseInt(msg.stream_id.replace('stream_', ''), 10);\n\n // nickname\uC774 \uBE48 \uBB38\uC790\uC5F4\uC774\uBA74 \uC2AC\uB86F \uD574\uC81C (stream \uCC38\uC870\uB294 \uBCF4\uC874)\n if (!msg.nickname || msg.nickname === '') {\n const existing = this._slots.get(slotIndex);\n if (existing) {\n this._slots.set(slotIndex, { slotIndex, stream: existing.stream });\n }\n this._emit('slot_remove', { slotIndex, senderId: msg.sender_id });\n return;\n }\n\n const existing = this._slots.get(slotIndex) || {};\n this._slots.set(slotIndex, {\n ...existing,\n slotIndex,\n streamId: msg.stream_id,\n nickname: msg.nickname,\n senderId: msg.sender_id,\n });\n this._emit('slot', this._slots.get(slotIndex));\n }\n\n /**\n * PeerConnection \uC0DD\uC131 + Non-Trickle ICE Offer.\n * old alloy-player \uAC80\uC99D \uD328\uD134:\n * 1. addTransceiver(recvonly) \u00D7 maxSlots \u2014 \uC11C\uBC84 m-line \uB9E4\uD551\n * 2. createOffer \u2192 H264 SDP \uAC15\uC81C\n * 3. ICE gathering \uC644\uB8CC \uB300\uAE30 \uD6C4 SDP \uC804\uCCB4 \uC804\uC1A1 (Non-Trickle)\n * @private\n */\n async _createPeerConnection() {\n this._pc = new RTCPeerConnection(this.rtcConfig);\n\n // Non-Trickle: ICE candidate\uB294 SDP\uC5D0 \uD3EC\uD568\uB418\uC5B4 \uC804\uC1A1\uB428\n // (onicecandidate\uB294 gathering \uCD94\uC801\uC6A9\uC73C\uB85C\uB9CC \uC720\uC9C0)\n\n // \uC218\uC2E0 \uD2B8\uB799 \uB9E4\uD551\n let videoTrackCount = 0;\n this._pc.ontrack = (event) => {\n const track = event.track;\n\n if (track.kind === 'video') {\n const slotIndex = videoTrackCount++;\n\n let stream = event.streams[0];\n if (!stream) {\n stream = new MediaStream();\n stream.addTrack(track);\n }\n\n const slot = this._slots.get(slotIndex) || { slotIndex };\n slot.stream = stream;\n this._slots.set(slotIndex, slot);\n\n this._emit('stream', stream, slotIndex);\n\n if (!this._connected) {\n this._connected = true;\n this._emit('connected');\n\n // \uC989\uC2DC \uD0A4\uD504\uB808\uC784 \uC694\uCCAD (\uCCAB \uD504\uB808\uC784 \uBE60\uB974\uAC8C)\n if (this._signaling) {\n this._signaling.sendPLI();\n }\n }\n }\n };\n\n this._pc.onconnectionstatechange = () => {\n const state = this._pc?.connectionState;\n if (state === 'failed' || state === 'disconnected') {\n this._emit('error', new Error(`PeerConnection ${state}`));\n }\n };\n\n // recvonly Transceiver \uCD94\uAC00 (\uC11C\uBC84 \uC2AC\uB86F \uC218\uC5D0 \uB9DE\uCDA4)\n for (let i = 0; i < this._maxSlots; i++) {\n this._pc.addTransceiver('video', { direction: 'recvonly' });\n this._pc.addTransceiver('audio', { direction: 'recvonly' });\n }\n\n // Offer \uC0DD\uC131 + H264/Opus \uCF54\uB371 \uAC15\uC81C\n const offer = await this._pc.createOffer();\n offer.sdp = FuzionXViewer._forceCodecs(offer.sdp);\n await this._pc.setLocalDescription(offer);\n\n // Non-Trickle ICE: gathering \uC644\uB8CC \uB300\uAE30 \uD6C4 \uC804\uCCB4 SDP \uC804\uC1A1\n await this._waitForIceGathering();\n\n // gathering \uC644\uB8CC \uD6C4 \uCD5C\uC885 SDP (ICE candidates \uD3EC\uD568) \uC804\uC1A1\n const finalSdp = this._pc.localDescription?.sdp;\n if (finalSdp) {\n this._signaling.sendOffer(finalSdp);\n }\n }\n\n /**\n * ICE gathering \uC644\uB8CC \uB300\uAE30 (Non-Trickle)\n * @private\n */\n _waitForIceGathering() {\n return new Promise((resolve) => {\n if (this._pc.iceGatheringState === 'complete') {\n return resolve();\n }\n const check = () => {\n if (this._pc?.iceGatheringState === 'complete') {\n this._pc.removeEventListener('icegatheringstatechange', check);\n resolve();\n }\n };\n this._pc.addEventListener('icegatheringstatechange', check);\n // 500ms \uD6C4 gathering \uC548 \uB05D\uB098\uBA74 \uD604\uC7AC SDP\uB85C \uC9C4\uD589\n setTimeout(() => {\n if (this._pc) {\n this._pc.removeEventListener('icegatheringstatechange', check);\n }\n resolve();\n }, 150);\n });\n }\n\n /**\n * SDP\uC5D0\uC11C H264 + Opus \uCF54\uB371 \uC6B0\uC120 \uAC15\uC81C.\n * @private\n */\n static _forceCodecs(sdp) {\n let lines = sdp.split('\\r\\n');\n\n // Video: H264 \uC6B0\uC120\n const videoIdx = lines.findIndex((l) => l.startsWith('m=video'));\n if (videoIdx !== -1) {\n const h264Pts = [];\n const h264Scores = new Map();\n lines.forEach((l) => {\n const m = l.match(/a=rtpmap:(\\d+) H264\\/90000/);\n if (m) {\n h264Pts.push(m[1]);\n h264Scores.set(m[1], 0);\n }\n });\n lines.forEach((l) => {\n if (l.startsWith('a=fmtp:')) {\n const pt = l.split(' ')[0].split(':')[1];\n if (h264Scores.has(pt)) {\n if (l.includes('profile-level-id=42e01f')) h264Scores.set(pt, 100);\n else if (l.includes('profile-level-id=42001f')) h264Scores.set(pt, 80);\n if (l.includes('packetization-mode=1')) h264Scores.set(pt, (h264Scores.get(pt) || 0) + 10);\n }\n }\n });\n if (h264Pts.length > 0) {\n h264Pts.sort((a, b) => h264Scores.get(b) - h264Scores.get(a));\n const parts = lines[videoIdx].split(' ');\n const otherPts = parts.slice(3).filter((pt) => !h264Pts.includes(pt));\n lines[videoIdx] = [...parts.slice(0, 3), ...h264Pts, ...otherPts].join(' ');\n }\n }\n\n // Audio: Opus \uC6B0\uC120\n const audioIdx = lines.findIndex((l) => l.startsWith('m=audio'));\n if (audioIdx !== -1) {\n const opusPts = [];\n lines.forEach((l) => {\n const m = l.match(/a=rtpmap:(\\d+) opus\\/48000/);\n if (m) opusPts.push(m[1]);\n });\n if (opusPts.length > 0) {\n const parts = lines[audioIdx].split(' ');\n const otherPts = parts.slice(3).filter((pt) => !opusPts.includes(pt));\n lines[audioIdx] = [...parts.slice(0, 3), ...opusPts, ...otherPts].join(' ');\n }\n }\n\n return lines.join('\\r\\n');\n }\n\n /** @private */\n _closePeerConnection() {\n if (this._pc) {\n this._pc.close();\n this._pc = null;\n }\n this._candidateQueue = [];\n }\n\n}\n", "/**\n * @fuzionx/player \u2014 FuzionXPublisher (\uC1A1\uCD9C\uC790 \uBAA8\uB4DC)\n *\n * \uB85C\uCEEC \uCE74\uBA54\uB77C/\uB9C8\uC774\uD06C \u2192 \uC11C\uBC84 \uC804\uC1A1.\n * 2\uAC00\uC9C0 \uBC29\uC2DD \uC9C0\uC6D0:\n * A. WebSocket \uBC29\uC2DD (videochat \uC591\uBC29\uD5A5)\n * B. WHIP \uBC29\uC2DD (OBS/\uB2E8\uBC29\uD5A5 \uBC29\uC1A1)\n *\n * @example WebSocket\n * const pub = new FuzionXPublisher({\n * url: 'wss://media:50002',\n * channelId: 'my-live',\n * mode: 'videochat',\n * nickname: '\uBC1C\uD45C\uC790',\n * });\n * pub.on('ready', () => console.log('Publishing!'));\n * pub.connect();\n *\n * @example WHIP\n * const pub = new FuzionXPublisher({\n * whipUrl: 'https://media:7777/whip/my-live',\n * });\n * pub.connect();\n */\n\nimport { FuzionXSignaling } from './FuzionXSignaling.js';\nimport { DEFAULT_ICE_SERVERS, SignalType, SessionMode, MAX_SLOTS } from './constants.js';\n\nexport class FuzionXPublisher {\n /**\n * @param {Object} opts\n * @param {string} [opts.url] - WebSocket URL (WS \uBC29\uC2DD)\n * @param {string} [opts.whipUrl] - WHIP URL (WHIP \uBC29\uC2DD)\n * @param {string} [opts.channelId] - \uCC44\uB110 ID (WS \uBC29\uC2DD \uD544\uC218)\n * @param {string} [opts.mode='broadcast'] - 'broadcast' | 'videochat'\n * @param {string} [opts.nickname] - \uB2C9\uB124\uC784\n * @param {string} [opts.token] - \uC778\uC99D \uD1A0\uD070\n * @param {string} [opts.peerId] - \uD53C\uC5B4 ID\n * @param {MediaStreamConstraints} [opts.media] - getUserMedia \uC81C\uC57D\n * @param {MediaStream} [opts.stream] - \uC774\uBBF8 \uD68D\uB4DD\uD55C MediaStream\n * @param {RTCConfiguration} [opts.rtcConfig] - WebRTC \uC124\uC815\n * @param {boolean} [opts.autoReconnect=true]\n */\n constructor(opts) {\n // WHIP or WebSocket\n this.whipUrl = opts.whipUrl || null;\n this.url = opts.url || null;\n this.hubUrl = opts.hubUrl || null;\n this.channelId = opts.channelId || null;\n this.mode = opts.mode || SessionMode.BROADCAST;\n this.nickname = opts.nickname || null;\n this.token = opts.token || null;\n this.peerId = opts.peerId || `pub-${Math.random().toString(36).slice(2, 10)}`;\n this.autoReconnect = opts.autoReconnect !== false;\n\n this.mediaConstraints = opts.media || { video: true, audio: true };\n this._externalStream = opts.stream || null;\n\n this.rtcConfig = opts.rtcConfig || {\n iceServers: DEFAULT_ICE_SERVERS,\n bundlePolicy: 'max-bundle',\n rtcpMuxPolicy: 'require',\n };\n\n /** @private */\n this._signaling = null;\n this._pc = null;\n this._localStream = null;\n this._listeners = {};\n this._candidateQueue = [];\n this._whipResourceUrl = null;\n this._maxSlots = MAX_SLOTS[this.mode] || 1;\n this._slots = new Map();\n this._connected = false;\n }\n\n // \u2500\u2500 Event System \u2500\u2500\n\n on(event, handler) {\n if (!this._listeners[event]) this._listeners[event] = [];\n this._listeners[event].push(handler);\n return this;\n }\n\n /** @private */\n _emit(event, ...args) {\n (this._listeners[event] || []).forEach((fn) => fn(...args));\n }\n\n // \u2500\u2500 Lifecycle \u2500\u2500\n\n /** \uC5F0\uACB0 \uC2DC\uC791 (\uBBF8\uB514\uC5B4 \uD68D\uB4DD \u2192 WebSocket \uB610\uB294 WHIP). */\n async connect() {\n try {\n // Hub \uB77C\uC6B0\uD305: hubUrl\uC774 \uC788\uC73C\uBA74 Hub\uC5D0\uC11C \uCC44\uB110 \uC0DD\uC131 (Origin \uD560\uB2F9)\n if (!this.url && !this.whipUrl && this.hubUrl && this.channelId) {\n // Publisher\uB294 POST \uC6B0\uC120: \uCC44\uB110 \uC0DD\uC131 \u2192 Origin ws_url \uBC18\uD658\n // GET\uC740 Viewer\uC6A9 (Edge \uBD84\uC0B0 \uD2B8\uB9AC\uAC70) \u2192 Publisher\uAC00 \uD638\uCD9C\uD558\uBA74 \uC548 \uB428\n const createRes = await fetch(`${this.hubUrl}/api/channels`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n channel_id: this.channelId,\n source_type: 'webrtc',\n }),\n });\n\n let data;\n if (createRes.status === 409) {\n // \uD654\uC0C1\uCC44\uD305: \uC774\uBBF8 \uC874\uC7AC\uD558\uB294 \uCC44\uB110\uC5D0 \uCD94\uAC00 Publisher \uC785\uC7A5\n // GET\uC73C\uB85C \uAE30\uC874 \uCC44\uB110 \uC815\uBCF4 \uC870\uD68C\n const getRes = await fetch(`${this.hubUrl}/api/channels/${this.channelId}`);\n if (!getRes.ok) throw new Error(`Channel not found: ${this.channelId}`);\n data = await getRes.json();\n } else if (!createRes.ok) {\n throw new Error(`Failed to create channel: ${this.channelId}`);\n } else {\n data = await createRes.json();\n }\n\n if (data.ws_url) {\n const isSecure = this.hubUrl.startsWith('https');\n this.url = data.ws_url.replace(/^ws(s?):/, isSecure ? 'wss:' : 'ws:');\n } else {\n const isSecure = this.hubUrl.startsWith('https');\n const wsProto = isSecure ? 'wss' : 'ws';\n this.url = `${wsProto}://${data.media_ip}:${data.webrtc_port}`;\n }\n }\n\n await this._acquireMedia();\n\n if (this.whipUrl) {\n await this._connectWhip();\n } else if (this.url) {\n this._connectWebSocket();\n } else {\n throw new Error('url, hubUrl, \uB610\uB294 whipUrl \uC911 \uD558\uB098\uB97C \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4.');\n }\n\n // \uD398\uC774\uC9C0 \uC774\uB3D9/\uD0ED \uB2EB\uAE30 \uC2DC \uC880\uBE44 \uBC29\uC9C0\n this._beforeUnloadHandler = () => {\n if (this._signaling && this._signaling.connected) {\n this._signaling.sendLeave();\n }\n this._closePeerConnection();\n this._stopMedia();\n };\n window.addEventListener('beforeunload', this._beforeUnloadHandler);\n } catch (e) {\n this._emit('error', e);\n }\n }\n\n /** \uC5F0\uACB0 \uC885\uB8CC. */\n async disconnect() {\n // beforeunload \uD574\uC81C\n if (this._beforeUnloadHandler) {\n window.removeEventListener('beforeunload', this._beforeUnloadHandler);\n this._beforeUnloadHandler = null;\n }\n\n if (this.whipUrl && this._whipResourceUrl) {\n // WHIP DELETE\n try {\n const baseUrl = new URL(this.whipUrl).origin;\n await fetch(`${baseUrl}${this._whipResourceUrl}`, { method: 'DELETE' });\n } catch (e) {\n console.warn('[FuzionX] WHIP DELETE error:', e);\n }\n this._whipResourceUrl = null;\n }\n\n if (this._signaling) {\n this._signaling.sendLeave();\n // Leave \uBA54\uC2DC\uC9C0\uAC00 flush\uB420 \uB54C\uAE4C\uC9C0 \uB300\uAE30 \uD6C4 WS \uB2EB\uAE30\n await new Promise(r => setTimeout(r, 100));\n this._signaling.disconnect();\n }\n\n this._closePeerConnection();\n this._stopMedia();\n this._connected = false;\n }\n\n /** \uCC44\uD305 \uC804\uC1A1. */\n chat(text) {\n if (this._signaling) {\n this._signaling.sendChat(text, this.nickname);\n }\n }\n\n /** @returns {MediaStream|null} \uB85C\uCEEC \uC2A4\uD2B8\uB9BC */\n get localStream() {\n return this._localStream;\n }\n\n /** @returns {Map} \uC2AC\uB86F \uC815\uBCF4 (videochat: \uB2E4\uB978 \uCC38\uAC00\uC790) */\n get slots() {\n return this._slots;\n }\n\n // \u2500\u2500 Media \u2500\u2500\n\n /** @private \uBBF8\uB514\uC5B4 \uD68D\uB4DD */\n async _acquireMedia() {\n if (this._externalStream) {\n this._localStream = this._externalStream;\n } else {\n this._localStream = await navigator.mediaDevices.getUserMedia(this.mediaConstraints);\n }\n this._emit('media', this._localStream);\n }\n\n /** @private */\n _stopMedia() {\n if (this._localStream && !this._externalStream) {\n this._localStream.getTracks().forEach((t) => t.stop());\n }\n this._localStream = null;\n }\n\n // \u2500\u2500 WHIP \u2500\u2500\n\n /** @private WHIP \uC5F0\uACB0 */\n async _connectWhip() {\n this._pc = new RTCPeerConnection(this.rtcConfig);\n\n // \uD2B8\uB799 \uCD94\uAC00\n this._localStream.getTracks().forEach((track) => {\n this._pc.addTrack(track, this._localStream);\n });\n\n // ICE Gathering \uC644\uB8CC \uB300\uAE30\n const offer = await this._pc.createOffer();\n offer.sdp = FuzionXPublisher._forceCodecs(offer.sdp);\n await this._pc.setLocalDescription(offer);\n\n // Gathering \uC644\uB8CC \uB300\uAE30\n await new Promise((resolve) => {\n if (this._pc.iceGatheringState === 'complete') {\n resolve();\n } else {\n this._pc.onicegatheringstatechange = () => {\n if (this._pc.iceGatheringState === 'complete') resolve();\n };\n // \uC548\uC804 \uD0C0\uC784\uC544\uC6C3\n setTimeout(resolve, 150);\n }\n });\n\n const localDesc = this._pc.localDescription;\n // Hub API\uC758 whip_url\uC740 \uC774\uBBF8 ?token=xxx \uD3EC\uD568 \uAC00\uB2A5\n let url = this.whipUrl;\n if (this.token && !url.includes('token=')) {\n url += (url.includes('?') ? '&' : '?') + `token=${this.token}`;\n }\n\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/sdp' },\n body: localDesc.sdp,\n });\n\n if (response.status !== 201) {\n throw new Error(`WHIP failed: ${response.status} ${await response.text()}`);\n }\n\n const answerSdp = await response.text();\n this._whipResourceUrl = response.headers.get('location');\n\n await this._pc.setRemoteDescription(\n new RTCSessionDescription({ type: 'answer', sdp: answerSdp })\n );\n\n this._pc.onconnectionstatechange = () => {\n const state = this._pc?.connectionState;\n if (state === 'connected') {\n this._connected = true;\n this._emit('ready');\n } else if (state === 'failed' || state === 'disconnected') {\n this._emit('error', new Error(`WHIP PeerConnection ${state}`));\n }\n };\n\n this._emit('ready');\n }\n\n // \u2500\u2500 WebSocket \u2500\u2500\n\n /** @private WS \uC5F0\uACB0 */\n _connectWebSocket() {\n this._signaling = new FuzionXSignaling({\n url: this.url,\n autoReconnect: this.autoReconnect,\n onOpen: () => this._onSignalingOpen(),\n onMessage: (msg) => this._onSignalingMessage(msg),\n onClose: (evt) => {\n this._closePeerConnection();\n this._connected = false;\n this._emit('close', evt);\n },\n onError: (err) => this._emit('error', err),\n });\n this._signaling.connect();\n }\n\n /** @private */\n async _onSignalingOpen() {\n this._signaling.sendJoin(this.peerId, this.channelId, {\n nickname: this.nickname,\n token: this.token,\n mode: this.mode,\n });\n await this._createPeerConnection();\n }\n\n /** @private */\n async _createPeerConnection() {\n this._pc = new RTCPeerConnection(this.rtcConfig);\n\n // Non-Trickle: ICE candidate\uB294 SDP\uC5D0 \uD3EC\uD568\n\n // \uB85C\uCEEC \uD2B8\uB799 \uCD94\uAC00 (\uC1A1\uCD9C)\n this._localStream.getTracks().forEach((track) => {\n this._pc.addTrack(track, this._localStream);\n });\n\n // videochat \uBAA8\uB4DC: \uC218\uC2E0 \uD2B8\uB799\uB3C4 \uC900\uBE44 (\uC591\uBC29\uD5A5)\n if (this.mode === SessionMode.VIDEOCHAT) {\n for (let i = 0; i < this._maxSlots; i++) {\n this._pc.addTransceiver('video', { direction: 'recvonly' });\n this._pc.addTransceiver('audio', { direction: 'recvonly' });\n }\n\n // \uC1A1\uCD9C \uD2B8\uB799\uC740 addTrack\uC73C\uB85C \uCD94\uAC00\uB428 \u2192 transceiver\uBC29\uD5A5\uC744 sendrecv\uB85C \uC5C5\uADF8\uB808\uC774\uB4DC\n const transceivers = this._pc.getTransceivers();\n transceivers.forEach((t) => {\n if (t.sender.track && t.direction === 'recvonly') {\n t.direction = 'sendrecv';\n }\n });\n\n let videoTrackCount = 0;\n this._pc.ontrack = (event) => {\n const track = event.track;\n if (track.kind === 'video') {\n const slotIndex = videoTrackCount++;\n let stream = event.streams[0];\n if (!stream) {\n stream = new MediaStream();\n stream.addTrack(track);\n }\n const slot = this._slots.get(slotIndex) || { slotIndex };\n slot.stream = stream;\n this._slots.set(slotIndex, slot);\n this._emit('stream', stream, slotIndex);\n }\n };\n }\n\n this._pc.onconnectionstatechange = () => {\n const state = this._pc?.connectionState;\n if (state === 'connected' && !this._connected) {\n this._connected = true;\n this._emit('ready');\n } else if (state === 'failed' || state === 'disconnected') {\n this._emit('error', new Error(`PeerConnection ${state}`));\n }\n };\n\n // Offer + H264/Opus SDP \uAC15\uC81C\n const offer = await this._pc.createOffer();\n offer.sdp = FuzionXPublisher._forceCodecs(offer.sdp);\n await this._pc.setLocalDescription(offer);\n\n // Non-Trickle: ICE gathering \uC644\uB8CC \uB300\uAE30\n await this._waitForIceGathering();\n\n const finalSdp = this._pc.localDescription?.sdp;\n if (finalSdp) {\n this._signaling.sendOffer(finalSdp);\n }\n }\n\n /** @private */\n _waitForIceGathering() {\n return new Promise((resolve) => {\n if (this._pc.iceGatheringState === 'complete') {\n return resolve();\n }\n const check = () => {\n if (this._pc?.iceGatheringState === 'complete') {\n this._pc.removeEventListener('icegatheringstatechange', check);\n resolve();\n }\n };\n this._pc.addEventListener('icegatheringstatechange', check);\n setTimeout(() => {\n if (this._pc) {\n this._pc.removeEventListener('icegatheringstatechange', check);\n }\n resolve();\n }, 150);\n });\n }\n\n /** @private */\n static _forceCodecs(sdp) {\n let lines = sdp.split('\\r\\n');\n const videoIdx = lines.findIndex((l) => l.startsWith('m=video'));\n if (videoIdx !== -1) {\n const h264Pts = [];\n const h264Scores = new Map();\n lines.forEach((l) => {\n const m = l.match(/a=rtpmap:(\\d+) H264\\/90000/);\n if (m) { h264Pts.push(m[1]); h264Scores.set(m[1], 0); }\n });\n lines.forEach((l) => {\n if (l.startsWith('a=fmtp:')) {\n const pt = l.split(' ')[0].split(':')[1];\n if (h264Scores.has(pt)) {\n if (l.includes('profile-level-id=42e01f')) h264Scores.set(pt, 100);\n else if (l.includes('profile-level-id=42001f')) h264Scores.set(pt, 80);\n if (l.includes('packetization-mode=1')) h264Scores.set(pt, (h264Scores.get(pt) || 0) + 10);\n }\n }\n });\n if (h264Pts.length > 0) {\n h264Pts.sort((a, b) => h264Scores.get(b) - h264Scores.get(a));\n const parts = lines[videoIdx].split(' ');\n const otherPts = parts.slice(3).filter((pt) => !h264Pts.includes(pt));\n lines[videoIdx] = [...parts.slice(0, 3), ...h264Pts, ...otherPts].join(' ');\n }\n }\n const audioIdx = lines.findIndex((l) => l.startsWith('m=audio'));\n if (audioIdx !== -1) {\n const opusPts = [];\n lines.forEach((l) => {\n const m = l.match(/a=rtpmap:(\\d+) opus\\/48000/);\n if (m) opusPts.push(m[1]);\n });\n if (opusPts.length > 0) {\n const parts = lines[audioIdx].split(' ');\n const otherPts = parts.slice(3).filter((pt) => !opusPts.includes(pt));\n lines[audioIdx] = [...parts.slice(0, 3), ...opusPts, ...otherPts].join(' ');\n }\n }\n return lines.join('\\r\\n');\n }\n\n /** @private */\n _onSignalingMessage(msg) {\n switch (msg.type) {\n case SignalType.ANSWER:\n this._handleAnswer(msg);\n break;\n case SignalType.CANDIDATE:\n this._handleCandidate(msg);\n break;\n case SignalType.SLOT_INFO:\n this._handleSlotInfo(msg);\n break;\n case SignalType.CHAT:\n this._emit('chat', {\n peerId: msg.peer_id,\n nickname: msg.nickname,\n text: msg.text,\n });\n break;\n case SignalType.ERROR:\n this._emit('error', new Error(msg.message));\n break;\n }\n }\n\n /** @private */\n async _handleAnswer(msg) {\n if (!this._pc) return;\n try {\n await this._pc.setRemoteDescription(\n new RTCSessionDescription({ type: 'answer', sdp: msg.sdp })\n );\n for (const c of this._candidateQueue) {\n await this._pc.addIceCandidate(c);\n }\n this._candidateQueue = [];\n } catch (e) {\n this._emit('error', e);\n }\n }\n\n /** @private */\n async _handleCandidate(msg) {\n const candidate = new RTCIceCandidate({\n candidate: msg.candidate,\n sdpMid: msg.sdp_mid,\n sdpMLineIndex: msg.sdp_m_line_index,\n });\n if (this._pc && this._pc.remoteDescription) {\n try { await this._pc.addIceCandidate(candidate); } catch (e) { /* ignore */ }\n } else {\n this._candidateQueue.push(candidate);\n }\n }\n\n /** @private */\n _handleSlotInfo(msg) {\n // \uC790\uAE30 \uC790\uC2E0\uC758 SlotInfo\uB294 \uBB34\uC2DC (\uC11C\uBC84\uB294 RTP\uB9CC self-skip, SlotInfo\uB294 \uBCF4\uB0C4)\n if (msg.sender_id === this.peerId) return;\n\n const slotIndex = parseInt(msg.stream_id.replace('stream_', ''), 10);\n if (!msg.nickname || msg.nickname === '') {\n // stream \uCC38\uC870\uB294 \uBCF4\uC874 (WebRTC \uD2B8\uB799\uC740 PC \uC218\uBA85\uACFC \uB3D9\uC77C)\n const existing = this._slots.get(slotIndex);\n if (existing) {\n this._slots.set(slotIndex, { slotIndex, stream: existing.stream });\n }\n this._emit('slot_remove', { slotIndex, senderId: msg.sender_id });\n return;\n }\n const existing = this._slots.get(slotIndex) || {};\n this._slots.set(slotIndex, {\n ...existing,\n slotIndex,\n streamId: msg.stream_id,\n nickname: msg.nickname,\n senderId: msg.sender_id,\n });\n this._emit('slot', this._slots.get(slotIndex));\n }\n\n /** @private */\n _closePeerConnection() {\n if (this._pc) {\n this._pc.close();\n this._pc = null;\n }\n this._candidateQueue = [];\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["DEFAULT_ICE_SERVERS", "SignalType", "SessionMode", "MAX_SLOTS", "RECONNECT", "CODEC", "FuzionXSignaling", "opts", "event", "msg", "peerId", "channelId", "SignalType", "sdp", "candidate", "text", "nickname", "RECONNECT", "delay", "FuzionXViewer", "_FuzionXViewer", "opts", "SessionMode", "DEFAULT_ICE_SERVERS", "MAX_SLOTS", "event", "handler", "args", "fn", "res", "data", "isSecure", "wsProto", "FuzionXSignaling", "msg", "evt", "err", "r", "text", "SignalType", "c", "e", "candidate", "slotIndex", "existing", "videoTrackCount", "track", "stream", "slot", "state", "i", "offer", "finalSdp", "resolve", "check", "sdp", "lines", "videoIdx", "l", "h264Pts", "h264Scores", "m", "pt", "a", "b", "parts", "otherPts", "audioIdx", "opusPts", "FuzionXPublisher", "_FuzionXPublisher", "opts", "SessionMode", "DEFAULT_ICE_SERVERS", "MAX_SLOTS", "event", "handler", "args", "fn", "createRes", "data", "getRes", "isSecure", "wsProto", "baseUrl", "r", "text", "t", "track", "offer", "resolve", "localDesc", "url", "response", "answerSdp", "state", "FuzionXSignaling", "msg", "evt", "err", "i", "videoTrackCount", "slotIndex", "stream", "slot", "finalSdp", "check", "sdp", "lines", "videoIdx", "l", "h264Pts", "h264Scores", "m", "pt", "a", "b", "parts", "otherPts", "audioIdx", "opusPts", "SignalType", "c", "e", "candidate", "existing"]
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
var FuzionXPlayer=(()=>{var S=Object.defineProperty;var E=Object.getOwnPropertyDescriptor;var I=Object.getOwnPropertyNames;var k=Object.prototype.hasOwnProperty;var T=(h,e)=>{for(var t in e)S(h,t,{get:e[t],enumerable:!0})},R=(h,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of I(e))!k.call(h,o)&&o!==t&&S(h,o,{get:()=>e[o],enumerable:!(i=E(e,o))||i.enumerable});return h};var y=h=>R(S({},"__esModule",{value:!0}),h);var O={};T(O,{CODEC:()=>C,DEFAULT_ICE_SERVERS:()=>u,FuzionXPublisher:()=>g,FuzionXSignaling:()=>_,FuzionXViewer:()=>m,MAX_SLOTS:()=>f,RECONNECT:()=>p,SessionMode:()=>d,SignalType:()=>c});var u=[{urls:"stun:stun.l.google.com:19302"},{urls:"stun:stun1.l.google.com:19302"}],c={JOIN:"join",OFFER:"offer",ANSWER:"answer",CANDIDATE:"candidate",PLI:"pli",LEAVE:"leave",SLOT_INFO:"slot_info",CHAT:"chat",ERROR:"error"},d={BROADCAST:"broadcast",VIDEOCHAT:"videochat"},f={[d.BROADCAST]:1,[d.VIDEOCHAT]:9},p={MAX_RETRIES:5,BASE_DELAY_MS:1e3,MAX_DELAY_MS:3e4},C={VIDEO_MIME:"video/H264",AUDIO_MIME:"audio/opus",VIDEO_CLOCK:9e4,AUDIO_CLOCK:48e3};var _=class{constructor(e){this.url=e.url,this.onMessage=e.onMessage||(()=>{}),this.onOpen=e.onOpen||(()=>{}),this.onClose=e.onClose||(()=>{}),this.onError=e.onError||(()=>{}),this.autoReconnect=e.autoReconnect!==!1,this._ws=null,this._retryCount=0,this._reconnectTimer=null,this._intentionalClose=!1}connect(){this._intentionalClose=!1,this._doConnect()}_doConnect(){try{this._ws=new WebSocket(this.url)}catch(e){this.onError(e),this._scheduleReconnect();return}this._ws.onopen=()=>{this._retryCount=0,this.onOpen()},this._ws.onmessage=e=>{try{let t=JSON.parse(e.data);this.onMessage(t)}catch{console.warn("[FuzionX] Invalid JSON:",e.data)}},this._ws.onclose=e=>{this.onClose(e),!this._intentionalClose&&this.autoReconnect&&this._scheduleReconnect()},this._ws.onerror=e=>{this.onError(e)}}send(e){return this._ws&&this._ws.readyState===WebSocket.OPEN?(this._ws.send(JSON.stringify(e)),!0):!1}sendJoin(e,t,i={}){return this.send({type:c.JOIN,peer_id:e,channel_id:t,nickname:i.nickname||null,token:i.token||null,mode:i.mode||null})}sendOffer(e){return this.send({type:c.OFFER,sdp:e})}sendAnswer(e){return this.send({type:c.ANSWER,sdp:e})}sendCandidate(e){return this.send({type:c.CANDIDATE,candidate:e.candidate,sdp_mid:e.sdpMid,sdp_m_line_index:e.sdpMLineIndex})}sendChat(e,t){return this.send({type:c.CHAT,text:e,nickname:t||null,peer_id:null})}sendPLI(){return this.send({type:c.PLI})}sendLeave(){return this.send({type:c.LEAVE})}disconnect(){this._intentionalClose=!0,clearTimeout(this._reconnectTimer),this._ws&&(this._ws.close(),this._ws=null)}get connected(){return this._ws&&this._ws.readyState===WebSocket.OPEN}_scheduleReconnect(){if(this._retryCount>=p.MAX_RETRIES){console.error("[FuzionX] Max reconnect retries reached."),this.onError(new Error("Max reconnect retries"));return}let e=Math.min(p.BASE_DELAY_MS*Math.pow(2,this._retryCount),p.MAX_DELAY_MS);this._retryCount++,console.log(`[FuzionX] Reconnecting in ${e}ms (${this._retryCount}/${p.MAX_RETRIES})`),this._reconnectTimer=setTimeout(()=>this._doConnect(),e)}};var m=class h{constructor(e){this.url=e.url||null,this.hubUrl=e.hubUrl||null,this.channelId=e.channelId,this.mode=e.mode||d.BROADCAST,this.nickname=e.nickname||null,this.token=e.token||null,this.peerId=e.peerId||`viewer-${Math.random().toString(36).slice(2,10)}`,this.autoReconnect=e.autoReconnect!==!1,this.rtcConfig=e.rtcConfig||{iceServers:u,bundlePolicy:"max-bundle",rtcpMuxPolicy:"require"},this._signaling=null,this._pc=null,this._listeners={},this._slots=new Map,this._maxSlots=f[this.mode]||1,this._candidateQueue=[],this._connected=!1}on(e,t){return this._listeners[e]||(this._listeners[e]=[]),this._listeners[e].push(t),this}_emit(e,...t){(this._listeners[e]||[]).forEach(i=>i(...t))}async connect(){if(!this.url&&this.hubUrl)try{let e=await fetch(`${this.hubUrl}/api/channels/${this.channelId}`);if(!e.ok)throw new Error(`Channel not found: ${this.channelId}`);let t=await e.json();if(t.ws_url){let i=this.hubUrl.startsWith("https");this.url=t.ws_url.replace(/^ws(s?):/,i?"wss:":"ws:")}else{let o=this.hubUrl.startsWith("https")?"wss":"ws";this.url=`${o}://${t.media_ip}:${t.webrtc_port}`}}catch(e){this._emit("error",e);return}if(!this.url){this._emit("error",new Error("url \uB610\uB294 hubUrl\uC744 \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4."));return}this._signaling=new _({url:this.url,autoReconnect:this.autoReconnect,onOpen:()=>this._onSignalingOpen(),onMessage:e=>this._onSignalingMessage(e),onClose:e=>this._onSignalingClose(e),onError:e=>this._emit("error",e)}),this._signaling.connect(),this._beforeUnloadHandler=()=>{this._signaling&&this._signaling.connected&&this._signaling.sendLeave(),this._closePeerConnection()},window.addEventListener("beforeunload",this._beforeUnloadHandler)}async disconnect(){this._beforeUnloadHandler&&(window.removeEventListener("beforeunload",this._beforeUnloadHandler),this._beforeUnloadHandler=null),this._signaling&&(this._signaling.sendLeave(),await new Promise(e=>setTimeout(e,100)),this._signaling.disconnect()),this._closePeerConnection(),this._slots.clear(),this._connected=!1}chat(e){this._signaling&&this._signaling.sendChat(e,this.nickname)}requestKeyframe(){this._signaling&&this._signaling.sendPLI()}get slots(){return this._slots}_onSignalingOpen(){this._signaling.sendJoin(this.peerId,this.channelId,{nickname:this.nickname,token:this.token,mode:this.mode}),this._createPeerConnection().catch(e=>this._emit("error",e))}_onSignalingMessage(e){switch(e.type){case c.ANSWER:this._handleAnswer(e);break;case c.CANDIDATE:this._handleCandidate(e);break;case c.SLOT_INFO:this._handleSlotInfo(e);break;case c.CHAT:this._emit("chat",{peerId:e.peer_id,nickname:e.nickname,text:e.text});break;case c.ERROR:this._emit("error",new Error(e.message));break}}_onSignalingClose(e){this._closePeerConnection(),this._connected=!1,this._emit("close",e)}async _handleAnswer(e){if(this._pc)try{await this._pc.setRemoteDescription(new RTCSessionDescription({type:"answer",sdp:e.sdp}));for(let t of this._candidateQueue)await this._pc.addIceCandidate(t);this._candidateQueue=[]}catch(t){this._emit("error",t)}}async _handleCandidate(e){let t=new RTCIceCandidate({candidate:e.candidate,sdpMid:e.sdp_mid,sdpMLineIndex:e.sdp_m_line_index});if(this._pc&&this._pc.remoteDescription)try{await this._pc.addIceCandidate(t)}catch(i){console.warn("[FuzionX] ICE candidate error:",i)}else this._candidateQueue.push(t)}_handleSlotInfo(e){let t=parseInt(e.stream_id.replace("stream_",""),10);if(!e.nickname||e.nickname===""){let o=this._slots.get(t);
|
|
1
|
+
var FuzionXPlayer=(()=>{var S=Object.defineProperty;var E=Object.getOwnPropertyDescriptor;var I=Object.getOwnPropertyNames;var k=Object.prototype.hasOwnProperty;var T=(h,e)=>{for(var t in e)S(h,t,{get:e[t],enumerable:!0})},R=(h,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of I(e))!k.call(h,o)&&o!==t&&S(h,o,{get:()=>e[o],enumerable:!(i=E(e,o))||i.enumerable});return h};var y=h=>R(S({},"__esModule",{value:!0}),h);var O={};T(O,{CODEC:()=>C,DEFAULT_ICE_SERVERS:()=>u,FuzionXPublisher:()=>g,FuzionXSignaling:()=>_,FuzionXViewer:()=>m,MAX_SLOTS:()=>f,RECONNECT:()=>p,SessionMode:()=>d,SignalType:()=>c});var u=[{urls:"stun:stun.l.google.com:19302"},{urls:"stun:stun1.l.google.com:19302"}],c={JOIN:"join",OFFER:"offer",ANSWER:"answer",CANDIDATE:"candidate",PLI:"pli",LEAVE:"leave",SLOT_INFO:"slot_info",CHAT:"chat",ERROR:"error"},d={BROADCAST:"broadcast",VIDEOCHAT:"videochat"},f={[d.BROADCAST]:1,[d.VIDEOCHAT]:9},p={MAX_RETRIES:5,BASE_DELAY_MS:1e3,MAX_DELAY_MS:3e4},C={VIDEO_MIME:"video/H264",AUDIO_MIME:"audio/opus",VIDEO_CLOCK:9e4,AUDIO_CLOCK:48e3};var _=class{constructor(e){this.url=e.url,this.onMessage=e.onMessage||(()=>{}),this.onOpen=e.onOpen||(()=>{}),this.onClose=e.onClose||(()=>{}),this.onError=e.onError||(()=>{}),this.autoReconnect=e.autoReconnect!==!1,this._ws=null,this._retryCount=0,this._reconnectTimer=null,this._intentionalClose=!1}connect(){this._intentionalClose=!1,this._doConnect()}_doConnect(){try{this._ws=new WebSocket(this.url)}catch(e){this.onError(e),this._scheduleReconnect();return}this._ws.onopen=()=>{this._retryCount=0,this.onOpen()},this._ws.onmessage=e=>{try{let t=JSON.parse(e.data);this.onMessage(t)}catch{console.warn("[FuzionX] Invalid JSON:",e.data)}},this._ws.onclose=e=>{this.onClose(e),!this._intentionalClose&&this.autoReconnect&&this._scheduleReconnect()},this._ws.onerror=e=>{this.onError(e)}}send(e){return this._ws&&this._ws.readyState===WebSocket.OPEN?(this._ws.send(JSON.stringify(e)),!0):!1}sendJoin(e,t,i={}){return this.send({type:c.JOIN,peer_id:e,channel_id:t,nickname:i.nickname||null,token:i.token||null,mode:i.mode||null})}sendOffer(e){return this.send({type:c.OFFER,sdp:e})}sendAnswer(e){return this.send({type:c.ANSWER,sdp:e})}sendCandidate(e){return this.send({type:c.CANDIDATE,candidate:e.candidate,sdp_mid:e.sdpMid,sdp_m_line_index:e.sdpMLineIndex})}sendChat(e,t){return this.send({type:c.CHAT,text:e,nickname:t||null,peer_id:null})}sendPLI(){return this.send({type:c.PLI})}sendLeave(){return this.send({type:c.LEAVE})}disconnect(){this._intentionalClose=!0,clearTimeout(this._reconnectTimer),this._ws&&(this._ws.close(),this._ws=null)}get connected(){return this._ws&&this._ws.readyState===WebSocket.OPEN}_scheduleReconnect(){if(this._retryCount>=p.MAX_RETRIES){console.error("[FuzionX] Max reconnect retries reached."),this.onError(new Error("Max reconnect retries"));return}let e=Math.min(p.BASE_DELAY_MS*Math.pow(2,this._retryCount),p.MAX_DELAY_MS);this._retryCount++,console.log(`[FuzionX] Reconnecting in ${e}ms (${this._retryCount}/${p.MAX_RETRIES})`),this._reconnectTimer=setTimeout(()=>this._doConnect(),e)}};var m=class h{constructor(e){this.url=e.url||null,this.hubUrl=e.hubUrl||null,this.channelId=e.channelId,this.mode=e.mode||d.BROADCAST,this.nickname=e.nickname||null,this.token=e.token||null,this.peerId=e.peerId||`viewer-${Math.random().toString(36).slice(2,10)}`,this.autoReconnect=e.autoReconnect!==!1,this.rtcConfig=e.rtcConfig||{iceServers:u,bundlePolicy:"max-bundle",rtcpMuxPolicy:"require"},this._signaling=null,this._pc=null,this._listeners={},this._slots=new Map,this._maxSlots=f[this.mode]||1,this._candidateQueue=[],this._connected=!1}on(e,t){return this._listeners[e]||(this._listeners[e]=[]),this._listeners[e].push(t),this}_emit(e,...t){(this._listeners[e]||[]).forEach(i=>i(...t))}async connect(){if(!this.url&&this.hubUrl)try{let e=await fetch(`${this.hubUrl}/api/channels/${this.channelId}`);if(!e.ok)throw new Error(`Channel not found: ${this.channelId}`);let t=await e.json();if(t.ws_url){let i=this.hubUrl.startsWith("https");this.url=t.ws_url.replace(/^ws(s?):/,i?"wss:":"ws:")}else{let o=this.hubUrl.startsWith("https")?"wss":"ws";this.url=`${o}://${t.media_ip}:${t.webrtc_port}`}}catch(e){this._emit("error",e);return}if(!this.url){this._emit("error",new Error("url \uB610\uB294 hubUrl\uC744 \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4."));return}this._signaling=new _({url:this.url,autoReconnect:this.autoReconnect,onOpen:()=>this._onSignalingOpen(),onMessage:e=>this._onSignalingMessage(e),onClose:e=>this._onSignalingClose(e),onError:e=>this._emit("error",e)}),this._signaling.connect(),this._beforeUnloadHandler=()=>{this._signaling&&this._signaling.connected&&this._signaling.sendLeave(),this._closePeerConnection()},window.addEventListener("beforeunload",this._beforeUnloadHandler)}async disconnect(){this._beforeUnloadHandler&&(window.removeEventListener("beforeunload",this._beforeUnloadHandler),this._beforeUnloadHandler=null),this._signaling&&(this._signaling.sendLeave(),await new Promise(e=>setTimeout(e,100)),this._signaling.disconnect()),this._closePeerConnection(),this._slots.clear(),this._connected=!1}chat(e){this._signaling&&this._signaling.sendChat(e,this.nickname)}requestKeyframe(){this._signaling&&this._signaling.sendPLI()}get slots(){return this._slots}_onSignalingOpen(){this._signaling.sendJoin(this.peerId,this.channelId,{nickname:this.nickname,token:this.token,mode:this.mode}),this._createPeerConnection().catch(e=>this._emit("error",e))}_onSignalingMessage(e){switch(e.type){case c.ANSWER:this._handleAnswer(e);break;case c.CANDIDATE:this._handleCandidate(e);break;case c.SLOT_INFO:this._handleSlotInfo(e);break;case c.CHAT:this._emit("chat",{peerId:e.peer_id,nickname:e.nickname,text:e.text});break;case c.ERROR:this._emit("error",new Error(e.message));break}}_onSignalingClose(e){this._closePeerConnection(),this._connected=!1,this._emit("close",e)}async _handleAnswer(e){if(this._pc)try{await this._pc.setRemoteDescription(new RTCSessionDescription({type:"answer",sdp:e.sdp}));for(let t of this._candidateQueue)await this._pc.addIceCandidate(t);this._candidateQueue=[]}catch(t){this._emit("error",t)}}async _handleCandidate(e){let t=new RTCIceCandidate({candidate:e.candidate,sdpMid:e.sdp_mid,sdpMLineIndex:e.sdp_m_line_index});if(this._pc&&this._pc.remoteDescription)try{await this._pc.addIceCandidate(t)}catch(i){console.warn("[FuzionX] ICE candidate error:",i)}else this._candidateQueue.push(t)}_handleSlotInfo(e){let t=parseInt(e.stream_id.replace("stream_",""),10);if(!e.nickname||e.nickname===""){let o=this._slots.get(t);o&&this._slots.set(t,{slotIndex:t,stream:o.stream}),this._emit("slot_remove",{slotIndex:t,senderId:e.sender_id});return}let i=this._slots.get(t)||{};this._slots.set(t,{...i,slotIndex:t,streamId:e.stream_id,nickname:e.nickname,senderId:e.sender_id}),this._emit("slot",this._slots.get(t))}async _createPeerConnection(){this._pc=new RTCPeerConnection(this.rtcConfig);let e=0;this._pc.ontrack=o=>{let n=o.track;if(n.kind==="video"){let s=e++,r=o.streams[0];r||(r=new MediaStream,r.addTrack(n));let a=this._slots.get(s)||{slotIndex:s};a.stream=r,this._slots.set(s,a),this._emit("stream",r,s),this._connected||(this._connected=!0,this._emit("connected"),this._signaling&&this._signaling.sendPLI())}},this._pc.onconnectionstatechange=()=>{let o=this._pc?.connectionState;(o==="failed"||o==="disconnected")&&this._emit("error",new Error(`PeerConnection ${o}`))};for(let o=0;o<this._maxSlots;o++)this._pc.addTransceiver("video",{direction:"recvonly"}),this._pc.addTransceiver("audio",{direction:"recvonly"});let t=await this._pc.createOffer();t.sdp=h._forceCodecs(t.sdp),await this._pc.setLocalDescription(t),await this._waitForIceGathering();let i=this._pc.localDescription?.sdp;i&&this._signaling.sendOffer(i)}_waitForIceGathering(){return new Promise(e=>{if(this._pc.iceGatheringState==="complete")return e();let t=()=>{this._pc?.iceGatheringState==="complete"&&(this._pc.removeEventListener("icegatheringstatechange",t),e())};this._pc.addEventListener("icegatheringstatechange",t),setTimeout(()=>{this._pc&&this._pc.removeEventListener("icegatheringstatechange",t),e()},150)})}static _forceCodecs(e){let t=e.split(`\r
|
|
2
2
|
`),i=t.findIndex(n=>n.startsWith("m=video"));if(i!==-1){let n=[],s=new Map;if(t.forEach(r=>{let a=r.match(/a=rtpmap:(\d+) H264\/90000/);a&&(n.push(a[1]),s.set(a[1],0))}),t.forEach(r=>{if(r.startsWith("a=fmtp:")){let a=r.split(" ")[0].split(":")[1];s.has(a)&&(r.includes("profile-level-id=42e01f")?s.set(a,100):r.includes("profile-level-id=42001f")&&s.set(a,80),r.includes("packetization-mode=1")&&s.set(a,(s.get(a)||0)+10))}}),n.length>0){n.sort((l,w)=>s.get(w)-s.get(l));let r=t[i].split(" "),a=r.slice(3).filter(l=>!n.includes(l));t[i]=[...r.slice(0,3),...n,...a].join(" ")}}let o=t.findIndex(n=>n.startsWith("m=audio"));if(o!==-1){let n=[];if(t.forEach(s=>{let r=s.match(/a=rtpmap:(\d+) opus\/48000/);r&&n.push(r[1])}),n.length>0){let s=t[o].split(" "),r=s.slice(3).filter(a=>!n.includes(a));t[o]=[...s.slice(0,3),...n,...r].join(" ")}}return t.join(`\r
|
|
3
3
|
`)}_closePeerConnection(){this._pc&&(this._pc.close(),this._pc=null),this._candidateQueue=[]}};var g=class h{constructor(e){this.whipUrl=e.whipUrl||null,this.url=e.url||null,this.hubUrl=e.hubUrl||null,this.channelId=e.channelId||null,this.mode=e.mode||d.BROADCAST,this.nickname=e.nickname||null,this.token=e.token||null,this.peerId=e.peerId||`pub-${Math.random().toString(36).slice(2,10)}`,this.autoReconnect=e.autoReconnect!==!1,this.mediaConstraints=e.media||{video:!0,audio:!0},this._externalStream=e.stream||null,this.rtcConfig=e.rtcConfig||{iceServers:u,bundlePolicy:"max-bundle",rtcpMuxPolicy:"require"},this._signaling=null,this._pc=null,this._localStream=null,this._listeners={},this._candidateQueue=[],this._whipResourceUrl=null,this._maxSlots=f[this.mode]||1,this._slots=new Map,this._connected=!1}on(e,t){return this._listeners[e]||(this._listeners[e]=[]),this._listeners[e].push(t),this}_emit(e,...t){(this._listeners[e]||[]).forEach(i=>i(...t))}async connect(){try{if(!this.url&&!this.whipUrl&&this.hubUrl&&this.channelId){let e=await fetch(`${this.hubUrl}/api/channels`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({channel_id:this.channelId,source_type:"webrtc"})}),t;if(e.status===409){let i=await fetch(`${this.hubUrl}/api/channels/${this.channelId}`);if(!i.ok)throw new Error(`Channel not found: ${this.channelId}`);t=await i.json()}else if(e.ok)t=await e.json();else throw new Error(`Failed to create channel: ${this.channelId}`);if(t.ws_url){let i=this.hubUrl.startsWith("https");this.url=t.ws_url.replace(/^ws(s?):/,i?"wss:":"ws:")}else{let o=this.hubUrl.startsWith("https")?"wss":"ws";this.url=`${o}://${t.media_ip}:${t.webrtc_port}`}}if(await this._acquireMedia(),this.whipUrl)await this._connectWhip();else if(this.url)this._connectWebSocket();else throw new Error("url, hubUrl, \uB610\uB294 whipUrl \uC911 \uD558\uB098\uB97C \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4.");this._beforeUnloadHandler=()=>{this._signaling&&this._signaling.connected&&this._signaling.sendLeave(),this._closePeerConnection(),this._stopMedia()},window.addEventListener("beforeunload",this._beforeUnloadHandler)}catch(e){this._emit("error",e)}}async disconnect(){if(this._beforeUnloadHandler&&(window.removeEventListener("beforeunload",this._beforeUnloadHandler),this._beforeUnloadHandler=null),this.whipUrl&&this._whipResourceUrl){try{let e=new URL(this.whipUrl).origin;await fetch(`${e}${this._whipResourceUrl}`,{method:"DELETE"})}catch(e){console.warn("[FuzionX] WHIP DELETE error:",e)}this._whipResourceUrl=null}this._signaling&&(this._signaling.sendLeave(),await new Promise(e=>setTimeout(e,100)),this._signaling.disconnect()),this._closePeerConnection(),this._stopMedia(),this._connected=!1}chat(e){this._signaling&&this._signaling.sendChat(e,this.nickname)}get localStream(){return this._localStream}get slots(){return this._slots}async _acquireMedia(){this._externalStream?this._localStream=this._externalStream:this._localStream=await navigator.mediaDevices.getUserMedia(this.mediaConstraints),this._emit("media",this._localStream)}_stopMedia(){this._localStream&&!this._externalStream&&this._localStream.getTracks().forEach(e=>e.stop()),this._localStream=null}async _connectWhip(){this._pc=new RTCPeerConnection(this.rtcConfig),this._localStream.getTracks().forEach(s=>{this._pc.addTrack(s,this._localStream)});let e=await this._pc.createOffer();e.sdp=h._forceCodecs(e.sdp),await this._pc.setLocalDescription(e),await new Promise(s=>{this._pc.iceGatheringState==="complete"?s():(this._pc.onicegatheringstatechange=()=>{this._pc.iceGatheringState==="complete"&&s()},setTimeout(s,150))});let t=this._pc.localDescription,i=this.whipUrl;this.token&&!i.includes("token=")&&(i+=(i.includes("?")?"&":"?")+`token=${this.token}`);let o=await fetch(i,{method:"POST",headers:{"Content-Type":"application/sdp"},body:t.sdp});if(o.status!==201)throw new Error(`WHIP failed: ${o.status} ${await o.text()}`);let n=await o.text();this._whipResourceUrl=o.headers.get("location"),await this._pc.setRemoteDescription(new RTCSessionDescription({type:"answer",sdp:n})),this._pc.onconnectionstatechange=()=>{let s=this._pc?.connectionState;s==="connected"?(this._connected=!0,this._emit("ready")):(s==="failed"||s==="disconnected")&&this._emit("error",new Error(`WHIP PeerConnection ${s}`))},this._emit("ready")}_connectWebSocket(){this._signaling=new _({url:this.url,autoReconnect:this.autoReconnect,onOpen:()=>this._onSignalingOpen(),onMessage:e=>this._onSignalingMessage(e),onClose:e=>{this._closePeerConnection(),this._connected=!1,this._emit("close",e)},onError:e=>this._emit("error",e)}),this._signaling.connect()}async _onSignalingOpen(){this._signaling.sendJoin(this.peerId,this.channelId,{nickname:this.nickname,token:this.token,mode:this.mode}),await this._createPeerConnection()}async _createPeerConnection(){if(this._pc=new RTCPeerConnection(this.rtcConfig),this._localStream.getTracks().forEach(i=>{this._pc.addTrack(i,this._localStream)}),this.mode===d.VIDEOCHAT){for(let n=0;n<this._maxSlots;n++)this._pc.addTransceiver("video",{direction:"recvonly"}),this._pc.addTransceiver("audio",{direction:"recvonly"});this._pc.getTransceivers().forEach(n=>{n.sender.track&&n.direction==="recvonly"&&(n.direction="sendrecv")});let o=0;this._pc.ontrack=n=>{let s=n.track;if(s.kind==="video"){let r=o++,a=n.streams[0];a||(a=new MediaStream,a.addTrack(s));let l=this._slots.get(r)||{slotIndex:r};l.stream=a,this._slots.set(r,l),this._emit("stream",a,r)}}}this._pc.onconnectionstatechange=()=>{let i=this._pc?.connectionState;i==="connected"&&!this._connected?(this._connected=!0,this._emit("ready")):(i==="failed"||i==="disconnected")&&this._emit("error",new Error(`PeerConnection ${i}`))};let e=await this._pc.createOffer();e.sdp=h._forceCodecs(e.sdp),await this._pc.setLocalDescription(e),await this._waitForIceGathering();let t=this._pc.localDescription?.sdp;t&&this._signaling.sendOffer(t)}_waitForIceGathering(){return new Promise(e=>{if(this._pc.iceGatheringState==="complete")return e();let t=()=>{this._pc?.iceGatheringState==="complete"&&(this._pc.removeEventListener("icegatheringstatechange",t),e())};this._pc.addEventListener("icegatheringstatechange",t),setTimeout(()=>{this._pc&&this._pc.removeEventListener("icegatheringstatechange",t),e()},150)})}static _forceCodecs(e){let t=e.split(`\r
|
|
4
4
|
`),i=t.findIndex(n=>n.startsWith("m=video"));if(i!==-1){let n=[],s=new Map;if(t.forEach(r=>{let a=r.match(/a=rtpmap:(\d+) H264\/90000/);a&&(n.push(a[1]),s.set(a[1],0))}),t.forEach(r=>{if(r.startsWith("a=fmtp:")){let a=r.split(" ")[0].split(":")[1];s.has(a)&&(r.includes("profile-level-id=42e01f")?s.set(a,100):r.includes("profile-level-id=42001f")&&s.set(a,80),r.includes("packetization-mode=1")&&s.set(a,(s.get(a)||0)+10))}}),n.length>0){n.sort((l,w)=>s.get(w)-s.get(l));let r=t[i].split(" "),a=r.slice(3).filter(l=>!n.includes(l));t[i]=[...r.slice(0,3),...n,...a].join(" ")}}let o=t.findIndex(n=>n.startsWith("m=audio"));if(o!==-1){let n=[];if(t.forEach(s=>{let r=s.match(/a=rtpmap:(\d+) opus\/48000/);r&&n.push(r[1])}),n.length>0){let s=t[o].split(" "),r=s.slice(3).filter(a=>!n.includes(a));t[o]=[...s.slice(0,3),...n,...r].join(" ")}}return t.join(`\r
|
|
5
|
-
`)}_onSignalingMessage(e){switch(e.type){case c.ANSWER:this._handleAnswer(e);break;case c.CANDIDATE:this._handleCandidate(e);break;case c.SLOT_INFO:this._handleSlotInfo(e);break;case c.CHAT:this._emit("chat",{peerId:e.peer_id,nickname:e.nickname,text:e.text});break;case c.ERROR:this._emit("error",new Error(e.message));break}}async _handleAnswer(e){if(this._pc)try{await this._pc.setRemoteDescription(new RTCSessionDescription({type:"answer",sdp:e.sdp}));for(let t of this._candidateQueue)await this._pc.addIceCandidate(t);this._candidateQueue=[]}catch(t){this._emit("error",t)}}async _handleCandidate(e){let t=new RTCIceCandidate({candidate:e.candidate,sdpMid:e.sdp_mid,sdpMLineIndex:e.sdp_m_line_index});if(this._pc&&this._pc.remoteDescription)try{await this._pc.addIceCandidate(t)}catch{}else this._candidateQueue.push(t)}_handleSlotInfo(e){if(e.sender_id===this.peerId)return;let t=parseInt(e.stream_id.replace("stream_",""),10);if(!e.nickname||e.nickname===""){let o=this._slots.get(t);
|
|
5
|
+
`)}_onSignalingMessage(e){switch(e.type){case c.ANSWER:this._handleAnswer(e);break;case c.CANDIDATE:this._handleCandidate(e);break;case c.SLOT_INFO:this._handleSlotInfo(e);break;case c.CHAT:this._emit("chat",{peerId:e.peer_id,nickname:e.nickname,text:e.text});break;case c.ERROR:this._emit("error",new Error(e.message));break}}async _handleAnswer(e){if(this._pc)try{await this._pc.setRemoteDescription(new RTCSessionDescription({type:"answer",sdp:e.sdp}));for(let t of this._candidateQueue)await this._pc.addIceCandidate(t);this._candidateQueue=[]}catch(t){this._emit("error",t)}}async _handleCandidate(e){let t=new RTCIceCandidate({candidate:e.candidate,sdpMid:e.sdp_mid,sdpMLineIndex:e.sdp_m_line_index});if(this._pc&&this._pc.remoteDescription)try{await this._pc.addIceCandidate(t)}catch{}else this._candidateQueue.push(t)}_handleSlotInfo(e){if(e.sender_id===this.peerId)return;let t=parseInt(e.stream_id.replace("stream_",""),10);if(!e.nickname||e.nickname===""){let o=this._slots.get(t);o&&this._slots.set(t,{slotIndex:t,stream:o.stream}),this._emit("slot_remove",{slotIndex:t,senderId:e.sender_id});return}let i=this._slots.get(t)||{};this._slots.set(t,{...i,slotIndex:t,streamId:e.stream_id,nickname:e.nickname,senderId:e.sender_id}),this._emit("slot",this._slots.get(t))}_closePeerConnection(){this._pc&&(this._pc.close(),this._pc=null),this._candidateQueue=[]}};return y(O);})();
|
|
6
6
|
if(typeof module!=="undefined")module.exports=FuzionXPlayer;
|
|
7
7
|
//# sourceMappingURL=fuzionx-player.umd.js.map
|