@fuzionx/player 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,135 @@
1
+ # @fuzionx/player
2
+
3
+ FuzionX WebRTC Player SDK — **3줄 코드**로 라이브 방송 시청/송출.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @fuzionx/player
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### 시청자 (Viewer)
14
+
15
+ ```javascript
16
+ import { FuzionXViewer } from '@fuzionx/player';
17
+
18
+ const viewer = new FuzionXViewer({
19
+ url: 'wss://media.example.com:50002',
20
+ channelId: 'my-channel',
21
+ mode: 'broadcast', // or 'videochat'
22
+ });
23
+
24
+ viewer.on('stream', (stream, slotIndex) => {
25
+ document.getElementById('video').srcObject = stream;
26
+ });
27
+
28
+ viewer.on('chat', ({ nickname, text }) => {
29
+ console.log(`${nickname}: ${text}`);
30
+ });
31
+
32
+ viewer.connect();
33
+ ```
34
+
35
+ ### 송출자 (Publisher) — 카메라
36
+
37
+ ```javascript
38
+ import { FuzionXPublisher } from '@fuzionx/player';
39
+
40
+ const pub = new FuzionXPublisher({
41
+ url: 'wss://media.example.com:50002',
42
+ channelId: 'my-channel',
43
+ mode: 'broadcast',
44
+ nickname: '방송자',
45
+ media: { video: true, audio: true },
46
+ });
47
+
48
+ pub.on('ready', () => console.log('Live!'));
49
+ pub.connect();
50
+ ```
51
+
52
+ ### 송출자 (Publisher) — WHIP (OBS)
53
+
54
+ ```javascript
55
+ import { FuzionXPublisher } from '@fuzionx/player';
56
+
57
+ const pub = new FuzionXPublisher({
58
+ whipUrl: 'https://media.example.com:7777/whip/my-channel',
59
+ token: 'your-jwt',
60
+ });
61
+
62
+ pub.on('ready', () => console.log('WHIP Connected!'));
63
+ pub.connect();
64
+ ```
65
+
66
+ ### 화상회의 (VideoChat)
67
+
68
+ ```javascript
69
+ import { FuzionXPublisher } from '@fuzionx/player';
70
+
71
+ const participant = new FuzionXPublisher({
72
+ url: 'wss://media.example.com:50002',
73
+ channelId: 'meeting-room',
74
+ mode: 'videochat',
75
+ nickname: '참가자1',
76
+ });
77
+
78
+ // 내 카메라
79
+ participant.on('media', (localStream) => {
80
+ document.getElementById('local').srcObject = localStream;
81
+ });
82
+
83
+ // 다른 참가자 스트림 수신
84
+ participant.on('stream', (stream, slotIndex) => {
85
+ document.getElementById(`remote-${slotIndex}`).srcObject = stream;
86
+ });
87
+
88
+ participant.on('slot', ({ slotIndex, nickname }) => {
89
+ console.log(`Slot ${slotIndex}: ${nickname}`);
90
+ });
91
+
92
+ participant.connect();
93
+ ```
94
+
95
+ ## CDN (Script Tag)
96
+
97
+ ```html
98
+ <script src="https://unpkg.com/@fuzionx/player/dist/fuzionx-player.umd.js"></script>
99
+ <script>
100
+ const { FuzionXViewer } = FuzionXPlayer;
101
+ const viewer = new FuzionXViewer({ ... });
102
+ </script>
103
+ ```
104
+
105
+ ## API
106
+
107
+ ### FuzionXViewer
108
+
109
+ | Option | Type | Default | Description |
110
+ |--------|------|---------|-------------|
111
+ | `url` | string | — | WebSocket URL |
112
+ | `channelId` | string | — | 채널 ID |
113
+ | `mode` | string | `'broadcast'` | `'broadcast'` or `'videochat'` |
114
+ | `nickname` | string | — | 닉네임 |
115
+ | `token` | string | — | 인증 토큰 |
116
+ | `autoReconnect` | boolean | `true` | 자동 재연결 |
117
+
118
+ **Events**: `stream`, `slot`, `slot_remove`, `chat`, `error`, `close`, `connected`
119
+
120
+ ### FuzionXPublisher
121
+
122
+ | Option | Type | Default | Description |
123
+ |--------|------|---------|-------------|
124
+ | `url` | string | — | WebSocket URL (WS 방식) |
125
+ | `whipUrl` | string | — | WHIP URL (단방향) |
126
+ | `channelId` | string | — | 채널 ID |
127
+ | `mode` | string | `'broadcast'` | `'broadcast'` or `'videochat'` |
128
+ | `media` | object | `{video:true, audio:true}` | getUserMedia 제약 |
129
+ | `stream` | MediaStream | — | 외부 스트림 |
130
+
131
+ **Events**: `ready`, `media`, `stream`, `slot`, `slot_remove`, `chat`, `error`, `close`
132
+
133
+ ## License
134
+
135
+ MIT
@@ -0,0 +1,6 @@
1
+ var u=[{urls:"stun:stun.l.google.com:19302"},{urls:"stun:stun1.l.google.com:19302"}],r={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},C={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,c={}){return this.send({type:r.JOIN,peer_id:e,channel_id:t,nickname:c.nickname||null,token:c.token||null,mode:c.mode||null})}sendOffer(e){return this.send({type:r.OFFER,sdp:e})}sendAnswer(e){return this.send({type:r.ANSWER,sdp:e})}sendCandidate(e){return this.send({type:r.CANDIDATE,candidate:e.candidate,sdp_mid:e.sdpMid,sdp_m_line_index:e.sdpMLineIndex})}sendChat(e,t){return this.send({type:r.CHAT,text:e,nickname:t||null,peer_id:null})}sendPLI(){return this.send({type:r.PLI})}sendLeave(){return this.send({type:r.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,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(c=>c(...t))}connect(){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()}disconnect(){this._signaling&&(this._signaling.sendLeave(),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 r.ANSWER:this._handleAnswer(e);break;case r.CANDIDATE:this._handleCandidate(e);break;case r.SLOT_INFO:this._handleSlotInfo(e);break;case r.CHAT:this._emit("chat",{peerId:e.peer_id,nickname:e.nickname,text:e.text});break;case r.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(c){console.warn("[FuzionX] ICE candidate error:",c)}else this._candidateQueue.push(t)}_handleSlotInfo(e){let t=parseInt(e.stream_id.replace("stream_",""),10);if(!e.nickname||e.nickname===""){this._slots.delete(t),this._emit("slot_remove",{slotIndex:t,senderId:e.sender_id});return}let c=this._slots.get(t)||{};this._slots.set(t,{...c,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 i=e++,s=a.streams[0];s||(s=new MediaStream,s.addTrack(n));let o=this._slots.get(i)||{slotIndex:i};o.stream=s,this._slots.set(i,o),this._emit("stream",s,i),this._connected||(this._connected=!0,this._emit("connected"))}},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 c=this._pc.localDescription?.sdp;c&&this._signaling.sendOffer(c)}_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()},3e3)})}static _forceCodecs(e){let t=e.split(`\r
2
+ `),c=t.findIndex(n=>n.startsWith("m=video"));if(c!==-1){let n=[],i=new Map;if(t.forEach(s=>{let o=s.match(/a=rtpmap:(\d+) H264\/90000/);o&&(n.push(o[1]),i.set(o[1],0))}),t.forEach(s=>{if(s.startsWith("a=fmtp:")){let o=s.split(" ")[0].split(":")[1];i.has(o)&&(s.includes("profile-level-id=42e01f")?i.set(o,100):s.includes("profile-level-id=42001f")&&i.set(o,80),s.includes("packetization-mode=1")&&i.set(o,(i.get(o)||0)+10))}}),n.length>0){n.sort((h,m)=>i.get(m)-i.get(h));let s=t[c].split(" "),o=s.slice(3).filter(h=>!n.includes(h));t[c]=[...s.slice(0,3),...n,...o].join(" ")}}let a=t.findIndex(n=>n.startsWith("m=audio"));if(a!==-1){let n=[];if(t.forEach(i=>{let s=i.match(/a=rtpmap:(\d+) opus\/48000/);s&&n.push(s[1])}),n.length>0){let i=t[a].split(" "),s=i.slice(3).filter(o=>!n.includes(o));t[a]=[...i.slice(0,3),...n,...s].join(" ")}}return t.join(`\r
3
+ `)}_closePeerConnection(){this._pc&&(this._pc.close(),this._pc=null),this._candidateQueue=[]}};var S=class p{constructor(e){this.whipUrl=e.whipUrl||null,this.url=e.url||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(c=>c(...t))}async connect(){try{if(await this._acquireMedia(),this.whipUrl)await this._connectWhip();else if(this.url)this._connectWebSocket();else throw new Error("url \uB610\uB294 whipUrl \uC911 \uD558\uB098\uB97C \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4.")}catch(e){this._emit("error",e)}}async disconnect(){if(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(),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(i=>{this._pc.addTrack(i,this._localStream)});let e=await this._pc.createOffer();e.sdp=p._forceCodecs(e.sdp),await this._pc.setLocalDescription(e),await new Promise(i=>{this._pc.iceGatheringState==="complete"?i():(this._pc.onicegatheringstatechange=()=>{this._pc.iceGatheringState==="complete"&&i()},setTimeout(i,3e3))});let t=this._pc.localDescription,c=this.whipUrl;this.token&&!c.includes("token=")&&(c+=(c.includes("?")?"&":"?")+`token=${this.token}`);let a=await fetch(c,{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 i=this._pc?.connectionState;i==="connected"?(this._connected=!0,this._emit("ready")):(i==="failed"||i==="disconnected")&&this._emit("error",new Error(`WHIP PeerConnection ${i}`))},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(c=>{this._pc.addTrack(c,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 i=n.track;if(i.kind==="video"){let s=a++,o=n.streams[0];o||(o=new MediaStream,o.addTrack(i));let h=this._slots.get(s)||{slotIndex:s};h.stream=o,this._slots.set(s,h),this._emit("stream",o,s)}}}this._pc.onconnectionstatechange=()=>{let c=this._pc?.connectionState;c==="connected"&&!this._connected?(this._connected=!0,this._emit("ready")):(c==="failed"||c==="disconnected")&&this._emit("error",new Error(`PeerConnection ${c}`))};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()},3e3)})}static _forceCodecs(e){let t=e.split(`\r
4
+ `),c=t.findIndex(n=>n.startsWith("m=video"));if(c!==-1){let n=[],i=new Map;if(t.forEach(s=>{let o=s.match(/a=rtpmap:(\d+) H264\/90000/);o&&(n.push(o[1]),i.set(o[1],0))}),t.forEach(s=>{if(s.startsWith("a=fmtp:")){let o=s.split(" ")[0].split(":")[1];i.has(o)&&(s.includes("profile-level-id=42e01f")?i.set(o,100):s.includes("profile-level-id=42001f")&&i.set(o,80),s.includes("packetization-mode=1")&&i.set(o,(i.get(o)||0)+10))}}),n.length>0){n.sort((h,m)=>i.get(m)-i.get(h));let s=t[c].split(" "),o=s.slice(3).filter(h=>!n.includes(h));t[c]=[...s.slice(0,3),...n,...o].join(" ")}}let a=t.findIndex(n=>n.startsWith("m=audio"));if(a!==-1){let n=[];if(t.forEach(i=>{let s=i.match(/a=rtpmap:(\d+) opus\/48000/);s&&n.push(s[1])}),n.length>0){let i=t[a].split(" "),s=i.slice(3).filter(o=>!n.includes(o));t[a]=[...i.slice(0,3),...n,...s].join(" ")}}return t.join(`\r
5
+ `)}_onSignalingMessage(e){switch(e.type){case r.ANSWER:this._handleAnswer(e);break;case r.CANDIDATE:this._handleCandidate(e);break;case r.SLOT_INFO:this._handleSlotInfo(e);break;case r.CHAT:this._emit("chat",{peerId:e.peer_id,nickname:e.nickname,text:e.text});break;case r.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){let t=parseInt(e.stream_id.replace("stream_",""),10);if(!e.nickname||e.nickname===""){this._slots.delete(t),this._emit("slot_remove",{slotIndex:t,senderId:e.sender_id});return}let c=this._slots.get(t)||{};this._slots.set(t,{...c,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{C as CODEC,u as DEFAULT_ICE_SERVERS,S as FuzionXPublisher,d as FuzionXSignaling,g as FuzionXViewer,f as MAX_SLOTS,_ as RECONNECT,l as SessionMode,r as SignalType};
6
+ //# sourceMappingURL=fuzionx-player.esm.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 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\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;\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 connect() {\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\n /** \uC5F0\uACB0 \uC885\uB8CC. */\n disconnect() {\n if (this._signaling) {\n this._signaling.sendLeave();\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\n if (!msg.nickname || msg.nickname === '') {\n this._slots.delete(slotIndex);\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 }\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 // Timeout 3s (Safari \uD638\uD658)\n setTimeout(() => {\n if (this._pc) {\n this._pc.removeEventListener('icegatheringstatechange', check);\n }\n resolve();\n }, 3000);\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.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 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 \uB610\uB294 whipUrl \uC911 \uD558\uB098\uB97C \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4.');\n }\n } catch (e) {\n this._emit('error', e);\n }\n }\n\n /** \uC5F0\uACB0 \uC885\uB8CC. */\n async disconnect() {\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 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, 3000);\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 }, 3000);\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 const slotIndex = parseInt(msg.stream_id.replace('stream_', ''), 10);\n if (!msg.nickname || msg.nickname === '') {\n this._slots.delete(slotIndex);\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
+ "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", "FuzionXSignaling", "msg", "evt", "err", "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", "baseUrl", "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
+ }
@@ -0,0 +1,7 @@
1
+ var FuzionXPlayer=(()=>{var C=Object.defineProperty;var E=Object.getOwnPropertyDescriptor;var I=Object.getOwnPropertyNames;var k=Object.prototype.hasOwnProperty;var T=(h,e)=>{for(var t in e)C(h,t,{get:e[t],enumerable:!0})},R=(h,e,t,c)=>{if(e&&typeof e=="object"||typeof e=="function")for(let a of I(e))!k.call(h,a)&&a!==t&&C(h,a,{get:()=>e[a],enumerable:!(c=E(e,a))||c.enumerable});return h};var y=h=>R(C({},"__esModule",{value:!0}),h);var O={};T(O,{CODEC:()=>w,DEFAULT_ICE_SERVERS:()=>u,FuzionXPublisher:()=>g,FuzionXSignaling:()=>_,FuzionXViewer:()=>m,MAX_SLOTS:()=>f,RECONNECT:()=>p,SessionMode:()=>d,SignalType:()=>r});var u=[{urls:"stun:stun.l.google.com:19302"},{urls:"stun:stun1.l.google.com:19302"}],r={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},w={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,c={}){return this.send({type:r.JOIN,peer_id:e,channel_id:t,nickname:c.nickname||null,token:c.token||null,mode:c.mode||null})}sendOffer(e){return this.send({type:r.OFFER,sdp:e})}sendAnswer(e){return this.send({type:r.ANSWER,sdp:e})}sendCandidate(e){return this.send({type:r.CANDIDATE,candidate:e.candidate,sdp_mid:e.sdpMid,sdp_m_line_index:e.sdpMLineIndex})}sendChat(e,t){return this.send({type:r.CHAT,text:e,nickname:t||null,peer_id:null})}sendPLI(){return this.send({type:r.PLI})}sendLeave(){return this.send({type:r.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,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(c=>c(...t))}connect(){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()}disconnect(){this._signaling&&(this._signaling.sendLeave(),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 r.ANSWER:this._handleAnswer(e);break;case r.CANDIDATE:this._handleCandidate(e);break;case r.SLOT_INFO:this._handleSlotInfo(e);break;case r.CHAT:this._emit("chat",{peerId:e.peer_id,nickname:e.nickname,text:e.text});break;case r.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(c){console.warn("[FuzionX] ICE candidate error:",c)}else this._candidateQueue.push(t)}_handleSlotInfo(e){let t=parseInt(e.stream_id.replace("stream_",""),10);if(!e.nickname||e.nickname===""){this._slots.delete(t),this._emit("slot_remove",{slotIndex:t,senderId:e.sender_id});return}let c=this._slots.get(t)||{};this._slots.set(t,{...c,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 i=e++,s=a.streams[0];s||(s=new MediaStream,s.addTrack(n));let o=this._slots.get(i)||{slotIndex:i};o.stream=s,this._slots.set(i,o),this._emit("stream",s,i),this._connected||(this._connected=!0,this._emit("connected"))}},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=h._forceCodecs(t.sdp),await this._pc.setLocalDescription(t),await this._waitForIceGathering();let c=this._pc.localDescription?.sdp;c&&this._signaling.sendOffer(c)}_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()},3e3)})}static _forceCodecs(e){let t=e.split(`\r
2
+ `),c=t.findIndex(n=>n.startsWith("m=video"));if(c!==-1){let n=[],i=new Map;if(t.forEach(s=>{let o=s.match(/a=rtpmap:(\d+) H264\/90000/);o&&(n.push(o[1]),i.set(o[1],0))}),t.forEach(s=>{if(s.startsWith("a=fmtp:")){let o=s.split(" ")[0].split(":")[1];i.has(o)&&(s.includes("profile-level-id=42e01f")?i.set(o,100):s.includes("profile-level-id=42001f")&&i.set(o,80),s.includes("packetization-mode=1")&&i.set(o,(i.get(o)||0)+10))}}),n.length>0){n.sort((l,S)=>i.get(S)-i.get(l));let s=t[c].split(" "),o=s.slice(3).filter(l=>!n.includes(l));t[c]=[...s.slice(0,3),...n,...o].join(" ")}}let a=t.findIndex(n=>n.startsWith("m=audio"));if(a!==-1){let n=[];if(t.forEach(i=>{let s=i.match(/a=rtpmap:(\d+) opus\/48000/);s&&n.push(s[1])}),n.length>0){let i=t[a].split(" "),s=i.slice(3).filter(o=>!n.includes(o));t[a]=[...i.slice(0,3),...n,...s].join(" ")}}return t.join(`\r
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.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(c=>c(...t))}async connect(){try{if(await this._acquireMedia(),this.whipUrl)await this._connectWhip();else if(this.url)this._connectWebSocket();else throw new Error("url \uB610\uB294 whipUrl \uC911 \uD558\uB098\uB97C \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4.")}catch(e){this._emit("error",e)}}async disconnect(){if(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(),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(i=>{this._pc.addTrack(i,this._localStream)});let e=await this._pc.createOffer();e.sdp=h._forceCodecs(e.sdp),await this._pc.setLocalDescription(e),await new Promise(i=>{this._pc.iceGatheringState==="complete"?i():(this._pc.onicegatheringstatechange=()=>{this._pc.iceGatheringState==="complete"&&i()},setTimeout(i,3e3))});let t=this._pc.localDescription,c=this.whipUrl;this.token&&!c.includes("token=")&&(c+=(c.includes("?")?"&":"?")+`token=${this.token}`);let a=await fetch(c,{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 i=this._pc?.connectionState;i==="connected"?(this._connected=!0,this._emit("ready")):(i==="failed"||i==="disconnected")&&this._emit("error",new Error(`WHIP PeerConnection ${i}`))},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(c=>{this._pc.addTrack(c,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 a=0;this._pc.ontrack=n=>{let i=n.track;if(i.kind==="video"){let s=a++,o=n.streams[0];o||(o=new MediaStream,o.addTrack(i));let l=this._slots.get(s)||{slotIndex:s};l.stream=o,this._slots.set(s,l),this._emit("stream",o,s)}}}this._pc.onconnectionstatechange=()=>{let c=this._pc?.connectionState;c==="connected"&&!this._connected?(this._connected=!0,this._emit("ready")):(c==="failed"||c==="disconnected")&&this._emit("error",new Error(`PeerConnection ${c}`))};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()},3e3)})}static _forceCodecs(e){let t=e.split(`\r
4
+ `),c=t.findIndex(n=>n.startsWith("m=video"));if(c!==-1){let n=[],i=new Map;if(t.forEach(s=>{let o=s.match(/a=rtpmap:(\d+) H264\/90000/);o&&(n.push(o[1]),i.set(o[1],0))}),t.forEach(s=>{if(s.startsWith("a=fmtp:")){let o=s.split(" ")[0].split(":")[1];i.has(o)&&(s.includes("profile-level-id=42e01f")?i.set(o,100):s.includes("profile-level-id=42001f")&&i.set(o,80),s.includes("packetization-mode=1")&&i.set(o,(i.get(o)||0)+10))}}),n.length>0){n.sort((l,S)=>i.get(S)-i.get(l));let s=t[c].split(" "),o=s.slice(3).filter(l=>!n.includes(l));t[c]=[...s.slice(0,3),...n,...o].join(" ")}}let a=t.findIndex(n=>n.startsWith("m=audio"));if(a!==-1){let n=[];if(t.forEach(i=>{let s=i.match(/a=rtpmap:(\d+) opus\/48000/);s&&n.push(s[1])}),n.length>0){let i=t[a].split(" "),s=i.slice(3).filter(o=>!n.includes(o));t[a]=[...i.slice(0,3),...n,...s].join(" ")}}return t.join(`\r
5
+ `)}_onSignalingMessage(e){switch(e.type){case r.ANSWER:this._handleAnswer(e);break;case r.CANDIDATE:this._handleCandidate(e);break;case r.SLOT_INFO:this._handleSlotInfo(e);break;case r.CHAT:this._emit("chat",{peerId:e.peer_id,nickname:e.nickname,text:e.text});break;case r.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){let t=parseInt(e.stream_id.replace("stream_",""),10);if(!e.nickname||e.nickname===""){this._slots.delete(t),this._emit("slot_remove",{slotIndex:t,senderId:e.sender_id});return}let c=this._slots.get(t)||{};this._slots.set(t,{...c,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
+ if(typeof module!=="undefined")module.exports=FuzionXPlayer;
7
+ //# sourceMappingURL=fuzionx-player.umd.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/index.js", "../src/constants.js", "../src/FuzionXSignaling.js", "../src/FuzionXViewer.js", "../src/FuzionXPublisher.js"],
4
+ "sourcesContent": ["/**\n * @fuzionx/player \u2014 Entry Point\n *\n * FuzionX WebRTC Player SDK\n */\n\nexport { FuzionXViewer } from './FuzionXViewer.js';\nexport { FuzionXPublisher } from './FuzionXPublisher.js';\nexport { FuzionXSignaling } from './FuzionXSignaling.js';\nexport { SignalType, SessionMode, MAX_SLOTS, DEFAULT_ICE_SERVERS, CODEC, RECONNECT } from './constants.js';\n", "/**\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\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;\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 connect() {\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\n /** \uC5F0\uACB0 \uC885\uB8CC. */\n disconnect() {\n if (this._signaling) {\n this._signaling.sendLeave();\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\n if (!msg.nickname || msg.nickname === '') {\n this._slots.delete(slotIndex);\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 }\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 // Timeout 3s (Safari \uD638\uD658)\n setTimeout(() => {\n if (this._pc) {\n this._pc.removeEventListener('icegatheringstatechange', check);\n }\n resolve();\n }, 3000);\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.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 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 \uB610\uB294 whipUrl \uC911 \uD558\uB098\uB97C \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4.');\n }\n } catch (e) {\n this._emit('error', e);\n }\n }\n\n /** \uC5F0\uACB0 \uC885\uB8CC. */\n async disconnect() {\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 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, 3000);\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 }, 3000);\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 const slotIndex = parseInt(msg.stream_id.replace('stream_', ''), 10);\n if (!msg.nickname || msg.nickname === '') {\n this._slots.delete(slotIndex);\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
+ "names": ["src_exports", "__export", "CODEC", "DEFAULT_ICE_SERVERS", "FuzionXPublisher", "FuzionXSignaling", "FuzionXViewer", "MAX_SLOTS", "RECONNECT", "SessionMode", "SignalType", "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", "FuzionXSignaling", "msg", "evt", "err", "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", "baseUrl", "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
+ }
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@fuzionx/player",
3
+ "version": "0.1.0",
4
+ "description": "FuzionX WebRTC Player SDK — Viewer & Publisher",
5
+ "main": "dist/fuzionx-player.umd.js",
6
+ "module": "dist/fuzionx-player.esm.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/fuzionx-player.esm.js",
11
+ "require": "./dist/fuzionx-player.umd.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist/",
16
+ "src/",
17
+ "README.md"
18
+ ],
19
+ "scripts": {
20
+ "dev": "node server.js",
21
+ "build": "node build.js",
22
+ "prepublishOnly": "node build.js"
23
+ },
24
+ "keywords": ["webrtc", "player", "streaming", "whip", "videochat", "fuzionx"],
25
+ "author": "FuzionX",
26
+ "license": "MIT",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/saytohenry/fuzionx",
30
+ "directory": "packages/fuzionx-player"
31
+ },
32
+ "devDependencies": {
33
+ "esbuild": "^0.21.0"
34
+ }
35
+ }