@fuzionx/player 0.1.53 → 0.1.55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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 (\uC9C1\uC811 \uC9C0\uC815)\n * @param {string} [opts.hubUrl] - Hub API URL (\uC790\uB3D9 \uB77C\uC6B0\uD305: Hub\uC5D0\uC11C \uBBF8\uB514\uC5B4 \uC11C\uBC84 \uC870\uD68C)\n * @param {string} opts.channelId - \uCC44\uB110 ID\n * @param {string} [opts.mode='broadcast'] - 'broadcast' | 'videochat'\n * @param {string} [opts.nickname] - \uB2C9\uB124\uC784\n * @param {string} [opts.token] - \uC778\uC99D \uD1A0\uD070\n * @param {string} [opts.peerId] - \uD53C\uC5B4 ID (\uC790\uB3D9 \uC0DD\uC131)\n * @param {RTCConfiguration} [opts.rtcConfig] - WebRTC \uC124\uC815 \uC624\uBC84\uB77C\uC774\uB4DC\n * @param {boolean} [opts.autoReconnect=true]\n */\n constructor(opts) {\n this.url = opts.url || null;\n this.hubUrl = opts.hubUrl || null;\n this.channelId = opts.channelId;\n this.mode = opts.mode || SessionMode.BROADCAST;\n this.nickname = opts.nickname || null;\n this.token = opts.token || null;\n this.peerId = opts.peerId || `viewer-${Math.random().toString(36).slice(2, 10)}`;\n this.autoReconnect = opts.autoReconnect !== false;\n\n this.rtcConfig = opts.rtcConfig || {\n iceServers: DEFAULT_ICE_SERVERS,\n bundlePolicy: 'max-bundle',\n rtcpMuxPolicy: 'require',\n };\n\n /** @private */\n this._signaling = null;\n this._pc = null;\n this._listeners = {};\n this._slots = new Map(); // slotIndex \u2192 { streamId, nickname, senderId, stream }\n this._maxSlots = MAX_SLOTS[this.mode] || 1;\n this._candidateQueue = [];\n this._connected = false;\n }\n\n // \u2500\u2500 Event System \u2500\u2500\n\n /**\n * \uC774\uBCA4\uD2B8 \uB9AC\uC2A4\uB108 \uB4F1\uB85D.\n * @param {'stream'|'slot'|'slot_remove'|'chat'|'error'|'close'|'connected'} event\n * @param {Function} handler\n */\n on(event, handler) {\n if (!this._listeners[event]) this._listeners[event] = [];\n this._listeners[event].push(handler);\n return this;\n }\n\n /** @private */\n _emit(event, ...args) {\n (this._listeners[event] || []).forEach((fn) => fn(...args));\n }\n\n // \u2500\u2500 Lifecycle \u2500\u2500\n\n /** \uC11C\uBC84 \uC5F0\uACB0 + WebRTC \uC138\uC158 \uC2DC\uC791. */\n async connect() {\n // Hub \uB77C\uC6B0\uD305: hubUrl\uC774 \uC788\uC73C\uBA74 Hub\uC5D0\uC11C \uBBF8\uB514\uC5B4 \uC11C\uBC84 \uC870\uD68C\n if (!this.url && this.hubUrl) {\n try {\n const res = await fetch(`${this.hubUrl}/api/channels/${this.channelId}`);\n if (!res.ok) throw new Error(`Channel not found: ${this.channelId}`);\n const data = await res.json();\n // ws_url \uC0AC\uC6A9 \uB610\uB294 ip:port\uC5D0\uC11C \uC0DD\uC131\n if (data.ws_url) {\n // Hub\uC640 \uAC19\uC740 \uD504\uB85C\uD1A0\uCF5C \uC0AC\uC6A9 (https\u2192wss, http\u2192ws)\n const isSecure = this.hubUrl.startsWith('https');\n this.url = data.ws_url.replace(/^ws(s?):/, isSecure ? 'wss:' : 'ws:');\n } else {\n const isSecure = this.hubUrl.startsWith('https');\n const wsProto = isSecure ? 'wss' : 'ws';\n this.url = `${wsProto}://${data.media_ip}:${data.webrtc_port}`;\n }\n } catch (e) {\n this._emit('error', e);\n return;\n }\n }\n\n if (!this.url) {\n this._emit('error', new Error('url \uB610\uB294 hubUrl\uC744 \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4.'));\n return;\n }\n\n this._signaling = new FuzionXSignaling({\n url: this.url,\n autoReconnect: this.autoReconnect,\n onOpen: () => this._onSignalingOpen(),\n onMessage: (msg) => this._onSignalingMessage(msg),\n onClose: (evt) => this._onSignalingClose(evt),\n onError: (err) => this._emit('error', err),\n });\n this._signaling.connect();\n\n // \uD398\uC774\uC9C0 \uC774\uB3D9/\uD0ED \uB2EB\uAE30 \uC2DC \uC880\uBE44 \uBC29\uC9C0\n this._beforeUnloadHandler = () => {\n if (this._signaling && this._signaling.connected) {\n this._signaling.sendLeave();\n }\n this._closePeerConnection();\n };\n window.addEventListener('beforeunload', this._beforeUnloadHandler);\n }\n\n /** \uC5F0\uACB0 \uC885\uB8CC. */\n async disconnect() {\n // beforeunload \uD574\uC81C\n if (this._beforeUnloadHandler) {\n window.removeEventListener('beforeunload', this._beforeUnloadHandler);\n this._beforeUnloadHandler = null;\n }\n\n if (this._signaling) {\n this._signaling.sendLeave();\n // Leave \uBA54\uC2DC\uC9C0\uAC00 flush\uB420 \uB54C\uAE4C\uC9C0 \uB300\uAE30 \uD6C4 WS \uB2EB\uAE30\n await new Promise(r => setTimeout(r, 100));\n this._signaling.disconnect();\n }\n this._closePeerConnection();\n this._slots.clear();\n this._connected = false;\n }\n\n /** \uCC44\uD305 \uC804\uC1A1. */\n chat(text) {\n if (this._signaling) {\n this._signaling.sendChat(text, this.nickname);\n }\n }\n\n /** \uD0A4\uD504\uB808\uC784 \uC694\uCCAD. */\n requestKeyframe() {\n if (this._signaling) {\n this._signaling.sendPLI();\n }\n }\n\n /** @returns {Map} \uD604\uC7AC \uC2AC\uB86F \uC815\uBCF4 */\n get slots() {\n return this._slots;\n }\n\n // \u2500\u2500 Internal \u2500\u2500\n\n /** @private */\n _onSignalingOpen() {\n this._signaling.sendJoin(this.peerId, this.channelId, {\n nickname: this.nickname,\n token: this.token,\n mode: this.mode,\n });\n // Join \uC804\uC1A1 \uD6C4 \uBC14\uB85C PeerConnection \uC0DD\uC131\n this._createPeerConnection().catch((e) => this._emit('error', e));\n }\n\n /** @private */\n _onSignalingMessage(msg) {\n switch (msg.type) {\n case SignalType.ANSWER:\n this._handleAnswer(msg);\n break;\n case SignalType.CANDIDATE:\n this._handleCandidate(msg);\n break;\n case SignalType.SLOT_INFO:\n this._handleSlotInfo(msg);\n break;\n case SignalType.CHAT:\n this._emit('chat', {\n peerId: msg.peer_id,\n nickname: msg.nickname,\n text: msg.text,\n });\n break;\n case SignalType.ERROR:\n this._emit('error', new Error(msg.message));\n break;\n }\n }\n\n /** @private */\n _onSignalingClose(evt) {\n this._closePeerConnection();\n this._connected = false;\n this._emit('close', evt);\n }\n\n /** @private */\n async _handleAnswer(msg) {\n if (!this._pc) return;\n try {\n await this._pc.setRemoteDescription(\n new RTCSessionDescription({ type: 'answer', sdp: msg.sdp })\n );\n // Flush queued ICE candidates\n for (const c of this._candidateQueue) {\n await this._pc.addIceCandidate(c);\n }\n this._candidateQueue = [];\n } catch (e) {\n this._emit('error', e);\n }\n }\n\n /** @private */\n async _handleCandidate(msg) {\n const candidate = new RTCIceCandidate({\n candidate: msg.candidate,\n sdpMid: msg.sdp_mid,\n sdpMLineIndex: msg.sdp_m_line_index,\n });\n if (this._pc && this._pc.remoteDescription) {\n try {\n await this._pc.addIceCandidate(candidate);\n } catch (e) {\n console.warn('[FuzionX] ICE candidate error:', e);\n }\n } else {\n this._candidateQueue.push(candidate);\n }\n }\n\n /** @private */\n _handleSlotInfo(msg) {\n const slotIndex = parseInt(msg.stream_id.replace('stream_', ''), 10);\n\n // nickname\uC774 \uBE48 \uBB38\uC790\uC5F4\uC774\uBA74 \uC2AC\uB86F \uD574\uC81C (stream \uCC38\uC870\uB294 \uBCF4\uC874)\n if (!msg.nickname || msg.nickname === '') {\n const existing = this._slots.get(slotIndex);\n if (existing) {\n this._slots.set(slotIndex, { slotIndex, stream: existing.stream });\n }\n this._emit('slot_remove', { slotIndex, senderId: msg.sender_id });\n return;\n }\n\n const existing = this._slots.get(slotIndex) || {};\n this._slots.set(slotIndex, {\n ...existing,\n slotIndex,\n streamId: msg.stream_id,\n nickname: msg.nickname,\n senderId: msg.sender_id,\n });\n this._emit('slot', this._slots.get(slotIndex));\n }\n\n /**\n * PeerConnection \uC0DD\uC131 + Non-Trickle ICE Offer.\n * old alloy-player \uAC80\uC99D \uD328\uD134:\n * 1. addTransceiver(recvonly) \u00D7 maxSlots \u2014 \uC11C\uBC84 m-line \uB9E4\uD551\n * 2. createOffer \u2192 H264 SDP \uAC15\uC81C\n * 3. ICE gathering \uC644\uB8CC \uB300\uAE30 \uD6C4 SDP \uC804\uCCB4 \uC804\uC1A1 (Non-Trickle)\n * @private\n */\n async _createPeerConnection() {\n this._pc = new RTCPeerConnection(this.rtcConfig);\n\n // Non-Trickle: ICE candidate\uB294 SDP\uC5D0 \uD3EC\uD568\uB418\uC5B4 \uC804\uC1A1\uB428\n // (onicecandidate\uB294 gathering \uCD94\uC801\uC6A9\uC73C\uB85C\uB9CC \uC720\uC9C0)\n\n // \uC218\uC2E0 \uD2B8\uB799 \uB9E4\uD551\n let videoTrackCount = 0;\n this._pc.ontrack = (event) => {\n const track = event.track;\n\n if (track.kind === 'video') {\n const slotIndex = videoTrackCount++;\n\n let stream = event.streams[0];\n if (!stream) {\n stream = new MediaStream();\n stream.addTrack(track);\n }\n\n const slot = this._slots.get(slotIndex) || { slotIndex };\n slot.stream = stream;\n this._slots.set(slotIndex, slot);\n\n this._emit('stream', stream, slotIndex);\n\n if (!this._connected) {\n this._connected = true;\n this._emit('connected');\n\n // \uC989\uC2DC \uD0A4\uD504\uB808\uC784 \uC694\uCCAD (\uCCAB \uD504\uB808\uC784 \uBE60\uB974\uAC8C)\n if (this._signaling) {\n this._signaling.sendPLI();\n }\n }\n }\n };\n\n this._pc.onconnectionstatechange = () => {\n const state = this._pc?.connectionState;\n if (state === 'failed' || state === 'disconnected') {\n this._emit('error', new Error(`PeerConnection ${state}`));\n }\n };\n\n // recvonly Transceiver \uCD94\uAC00 (\uC11C\uBC84 \uC2AC\uB86F \uC218\uC5D0 \uB9DE\uCDA4)\n for (let i = 0; i < this._maxSlots; i++) {\n this._pc.addTransceiver('video', { direction: 'recvonly' });\n this._pc.addTransceiver('audio', { direction: 'recvonly' });\n }\n\n // Offer \uC0DD\uC131 + H264/Opus \uCF54\uB371 \uAC15\uC81C\n const offer = await this._pc.createOffer();\n offer.sdp = FuzionXViewer._forceCodecs(offer.sdp);\n await this._pc.setLocalDescription(offer);\n\n // Non-Trickle ICE: gathering \uC644\uB8CC \uB300\uAE30 \uD6C4 \uC804\uCCB4 SDP \uC804\uC1A1\n await this._waitForIceGathering();\n\n // gathering \uC644\uB8CC \uD6C4 \uCD5C\uC885 SDP (ICE candidates \uD3EC\uD568) \uC804\uC1A1\n const finalSdp = this._pc.localDescription?.sdp;\n if (finalSdp) {\n this._signaling.sendOffer(finalSdp);\n }\n }\n\n /**\n * ICE gathering \uC644\uB8CC \uB300\uAE30 (Non-Trickle)\n * @private\n */\n _waitForIceGathering() {\n return new Promise((resolve) => {\n if (this._pc.iceGatheringState === 'complete') {\n return resolve();\n }\n const check = () => {\n if (this._pc?.iceGatheringState === 'complete') {\n this._pc.removeEventListener('icegatheringstatechange', check);\n resolve();\n }\n };\n this._pc.addEventListener('icegatheringstatechange', check);\n // 500ms \uD6C4 gathering \uC548 \uB05D\uB098\uBA74 \uD604\uC7AC SDP\uB85C \uC9C4\uD589\n setTimeout(() => {\n if (this._pc) {\n this._pc.removeEventListener('icegatheringstatechange', check);\n }\n resolve();\n }, 150);\n });\n }\n\n /**\n * SDP\uC5D0\uC11C H264 + Opus \uCF54\uB371 \uC6B0\uC120 \uAC15\uC81C.\n * @private\n */\n static _forceCodecs(sdp) {\n let lines = sdp.split('\\r\\n');\n\n // Video: H264 \uC6B0\uC120\n const videoIdx = lines.findIndex((l) => l.startsWith('m=video'));\n if (videoIdx !== -1) {\n const h264Pts = [];\n const h264Scores = new Map();\n lines.forEach((l) => {\n const m = l.match(/a=rtpmap:(\\d+) H264\\/90000/);\n if (m) {\n h264Pts.push(m[1]);\n h264Scores.set(m[1], 0);\n }\n });\n lines.forEach((l) => {\n if (l.startsWith('a=fmtp:')) {\n const pt = l.split(' ')[0].split(':')[1];\n if (h264Scores.has(pt)) {\n if (l.includes('profile-level-id=42e01f')) h264Scores.set(pt, 100);\n else if (l.includes('profile-level-id=42001f')) h264Scores.set(pt, 80);\n if (l.includes('packetization-mode=1')) h264Scores.set(pt, (h264Scores.get(pt) || 0) + 10);\n }\n }\n });\n if (h264Pts.length > 0) {\n h264Pts.sort((a, b) => h264Scores.get(b) - h264Scores.get(a));\n const parts = lines[videoIdx].split(' ');\n const otherPts = parts.slice(3).filter((pt) => !h264Pts.includes(pt));\n lines[videoIdx] = [...parts.slice(0, 3), ...h264Pts, ...otherPts].join(' ');\n }\n }\n\n // Audio: Opus \uC6B0\uC120\n const audioIdx = lines.findIndex((l) => l.startsWith('m=audio'));\n if (audioIdx !== -1) {\n const opusPts = [];\n lines.forEach((l) => {\n const m = l.match(/a=rtpmap:(\\d+) opus\\/48000/);\n if (m) opusPts.push(m[1]);\n });\n if (opusPts.length > 0) {\n const parts = lines[audioIdx].split(' ');\n const otherPts = parts.slice(3).filter((pt) => !opusPts.includes(pt));\n lines[audioIdx] = [...parts.slice(0, 3), ...opusPts, ...otherPts].join(' ');\n }\n }\n\n return lines.join('\\r\\n');\n }\n\n /** @private */\n _closePeerConnection() {\n if (this._pc) {\n this._pc.close();\n this._pc = null;\n }\n this._candidateQueue = [];\n }\n\n}\n", "/**\n * @fuzionx/player \u2014 FuzionXPublisher (\uC1A1\uCD9C\uC790 \uBAA8\uB4DC)\n *\n * \uB85C\uCEEC \uCE74\uBA54\uB77C/\uB9C8\uC774\uD06C \u2192 \uC11C\uBC84 \uC804\uC1A1.\n * 2\uAC00\uC9C0 \uBC29\uC2DD \uC9C0\uC6D0:\n * A. WebSocket \uBC29\uC2DD (videochat \uC591\uBC29\uD5A5)\n * B. WHIP \uBC29\uC2DD (OBS/\uB2E8\uBC29\uD5A5 \uBC29\uC1A1)\n *\n * @example WebSocket\n * const pub = new FuzionXPublisher({\n * url: 'wss://media:50002',\n * channelId: 'my-live',\n * mode: 'videochat',\n * nickname: '\uBC1C\uD45C\uC790',\n * });\n * pub.on('ready', () => console.log('Publishing!'));\n * pub.connect();\n *\n * @example WHIP\n * const pub = new FuzionXPublisher({\n * whipUrl: 'https://media:7777/whip/my-live',\n * });\n * pub.connect();\n */\n\nimport { FuzionXSignaling } from './FuzionXSignaling.js';\nimport { DEFAULT_ICE_SERVERS, SignalType, SessionMode, MAX_SLOTS } from './constants.js';\n\nexport class FuzionXPublisher {\n /**\n * @param {Object} opts\n * @param {string} [opts.url] - WebSocket URL (WS \uBC29\uC2DD)\n * @param {string} [opts.whipUrl] - WHIP URL (WHIP \uBC29\uC2DD)\n * @param {string} [opts.channelId] - \uCC44\uB110 ID (WS \uBC29\uC2DD \uD544\uC218)\n * @param {string} [opts.mode='broadcast'] - 'broadcast' | 'videochat'\n * @param {string} [opts.nickname] - \uB2C9\uB124\uC784\n * @param {string} [opts.token] - \uC778\uC99D \uD1A0\uD070\n * @param {string} [opts.peerId] - \uD53C\uC5B4 ID\n * @param {MediaStreamConstraints} [opts.media] - getUserMedia \uC81C\uC57D\n * @param {MediaStream} [opts.stream] - \uC774\uBBF8 \uD68D\uB4DD\uD55C MediaStream\n * @param {RTCConfiguration} [opts.rtcConfig] - WebRTC \uC124\uC815\n * @param {boolean} [opts.autoReconnect=true]\n */\n constructor(opts) {\n // WHIP or WebSocket\n this.whipUrl = opts.whipUrl || null;\n this.url = opts.url || null;\n this.hubUrl = opts.hubUrl || null;\n this.channelId = opts.channelId || null;\n this.mode = opts.mode || SessionMode.BROADCAST;\n this.nickname = opts.nickname || null;\n this.token = opts.token || null;\n this.peerId = opts.peerId || `pub-${Math.random().toString(36).slice(2, 10)}`;\n this.autoReconnect = opts.autoReconnect !== false;\n\n this.mediaConstraints = opts.media || { video: true, audio: true };\n this._externalStream = opts.stream || null;\n\n this.rtcConfig = opts.rtcConfig || {\n iceServers: DEFAULT_ICE_SERVERS,\n bundlePolicy: 'max-bundle',\n rtcpMuxPolicy: 'require',\n };\n\n /** @private */\n this._signaling = null;\n this._pc = null;\n this._localStream = null;\n this._listeners = {};\n this._candidateQueue = [];\n this._whipResourceUrl = null;\n this._maxSlots = MAX_SLOTS[this.mode] || 1;\n this._slots = new Map();\n this._connected = false;\n }\n\n // \u2500\u2500 Event System \u2500\u2500\n\n on(event, handler) {\n if (!this._listeners[event]) this._listeners[event] = [];\n this._listeners[event].push(handler);\n return this;\n }\n\n /** @private */\n _emit(event, ...args) {\n (this._listeners[event] || []).forEach((fn) => fn(...args));\n }\n\n // \u2500\u2500 Lifecycle \u2500\u2500\n\n /** \uC5F0\uACB0 \uC2DC\uC791 (\uBBF8\uB514\uC5B4 \uD68D\uB4DD \u2192 WebSocket \uB610\uB294 WHIP). */\n async connect() {\n try {\n // Hub \uB77C\uC6B0\uD305: hubUrl\uC774 \uC788\uC73C\uBA74 Hub\uC5D0\uC11C \uCC44\uB110 \uC0DD\uC131 (Origin \uD560\uB2F9)\n if (!this.url && !this.whipUrl && this.hubUrl && this.channelId) {\n // Publisher\uB294 POST \uC6B0\uC120: \uCC44\uB110 \uC0DD\uC131 \u2192 Origin ws_url \uBC18\uD658\n // GET\uC740 Viewer\uC6A9 (Edge \uBD84\uC0B0 \uD2B8\uB9AC\uAC70) \u2192 Publisher\uAC00 \uD638\uCD9C\uD558\uBA74 \uC548 \uB428\n const createRes = await fetch(`${this.hubUrl}/api/channels`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n channel_id: this.channelId,\n source_type: 'webrtc',\n }),\n });\n\n let data;\n if (createRes.status === 409) {\n // \uD654\uC0C1\uCC44\uD305: \uC774\uBBF8 \uC874\uC7AC\uD558\uB294 \uCC44\uB110\uC5D0 \uCD94\uAC00 Publisher \uC785\uC7A5\n // GET\uC73C\uB85C \uAE30\uC874 \uCC44\uB110 \uC815\uBCF4 \uC870\uD68C\n const getRes = await fetch(`${this.hubUrl}/api/channels/${this.channelId}`);\n if (!getRes.ok) throw new Error(`Channel not found: ${this.channelId}`);\n data = await getRes.json();\n } else if (!createRes.ok) {\n throw new Error(`Failed to create channel: ${this.channelId}`);\n } else {\n data = await createRes.json();\n }\n\n if (data.ws_url) {\n const isSecure = this.hubUrl.startsWith('https');\n this.url = data.ws_url.replace(/^ws(s?):/, isSecure ? 'wss:' : 'ws:');\n } else {\n const isSecure = this.hubUrl.startsWith('https');\n const wsProto = isSecure ? 'wss' : 'ws';\n this.url = `${wsProto}://${data.media_ip}:${data.webrtc_port}`;\n }\n }\n\n await this._acquireMedia();\n\n if (this.whipUrl) {\n await this._connectWhip();\n } else if (this.url) {\n this._connectWebSocket();\n } else {\n throw new Error('url, hubUrl, \uB610\uB294 whipUrl \uC911 \uD558\uB098\uB97C \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4.');\n }\n\n // \uD398\uC774\uC9C0 \uC774\uB3D9/\uD0ED \uB2EB\uAE30 \uC2DC \uC880\uBE44 \uBC29\uC9C0\n this._beforeUnloadHandler = () => {\n if (this._signaling && this._signaling.connected) {\n this._signaling.sendLeave();\n }\n this._closePeerConnection();\n this._stopMedia();\n };\n window.addEventListener('beforeunload', this._beforeUnloadHandler);\n } catch (e) {\n this._emit('error', e);\n }\n }\n\n /** \uC5F0\uACB0 \uC885\uB8CC. */\n async disconnect() {\n // beforeunload \uD574\uC81C\n if (this._beforeUnloadHandler) {\n window.removeEventListener('beforeunload', this._beforeUnloadHandler);\n this._beforeUnloadHandler = null;\n }\n\n if (this.whipUrl && this._whipResourceUrl) {\n // WHIP DELETE\n try {\n const baseUrl = new URL(this.whipUrl).origin;\n await fetch(`${baseUrl}${this._whipResourceUrl}`, { method: 'DELETE' });\n } catch (e) {\n console.warn('[FuzionX] WHIP DELETE error:', e);\n }\n this._whipResourceUrl = null;\n }\n\n if (this._signaling) {\n this._signaling.sendLeave();\n // Leave \uBA54\uC2DC\uC9C0\uAC00 flush\uB420 \uB54C\uAE4C\uC9C0 \uB300\uAE30 \uD6C4 WS \uB2EB\uAE30\n await new Promise(r => setTimeout(r, 100));\n this._signaling.disconnect();\n }\n\n this._closePeerConnection();\n this._stopMedia();\n this._connected = false;\n }\n\n /** \uCC44\uD305 \uC804\uC1A1. */\n chat(text) {\n if (this._signaling) {\n this._signaling.sendChat(text, this.nickname);\n }\n }\n\n /** @returns {MediaStream|null} \uB85C\uCEEC \uC2A4\uD2B8\uB9BC */\n get localStream() {\n return this._localStream;\n }\n\n /** @returns {Map} \uC2AC\uB86F \uC815\uBCF4 (videochat: \uB2E4\uB978 \uCC38\uAC00\uC790) */\n get slots() {\n return this._slots;\n }\n\n // \u2500\u2500 Media \u2500\u2500\n\n /** @private \uBBF8\uB514\uC5B4 \uD68D\uB4DD */\n async _acquireMedia() {\n if (this._externalStream) {\n this._localStream = this._externalStream;\n } else {\n this._localStream = await navigator.mediaDevices.getUserMedia(this.mediaConstraints);\n }\n this._emit('media', this._localStream);\n }\n\n /** @private */\n _stopMedia() {\n if (this._localStream && !this._externalStream) {\n this._localStream.getTracks().forEach((t) => t.stop());\n }\n this._localStream = null;\n }\n\n // \u2500\u2500 WHIP \u2500\u2500\n\n /** @private WHIP \uC5F0\uACB0 */\n async _connectWhip() {\n this._pc = new RTCPeerConnection(this.rtcConfig);\n\n // \uD2B8\uB799 \uCD94\uAC00\n this._localStream.getTracks().forEach((track) => {\n this._pc.addTrack(track, this._localStream);\n });\n\n // ICE Gathering \uC644\uB8CC \uB300\uAE30\n const offer = await this._pc.createOffer();\n offer.sdp = FuzionXPublisher._forceCodecs(offer.sdp);\n await this._pc.setLocalDescription(offer);\n\n // Gathering \uC644\uB8CC \uB300\uAE30\n await new Promise((resolve) => {\n if (this._pc.iceGatheringState === 'complete') {\n resolve();\n } else {\n this._pc.onicegatheringstatechange = () => {\n if (this._pc.iceGatheringState === 'complete') resolve();\n };\n // \uC548\uC804 \uD0C0\uC784\uC544\uC6C3\n setTimeout(resolve, 150);\n }\n });\n\n const localDesc = this._pc.localDescription;\n // Hub API\uC758 whip_url\uC740 \uC774\uBBF8 ?token=xxx \uD3EC\uD568 \uAC00\uB2A5\n let url = this.whipUrl;\n if (this.token && !url.includes('token=')) {\n url += (url.includes('?') ? '&' : '?') + `token=${this.token}`;\n }\n\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/sdp' },\n body: localDesc.sdp,\n });\n\n if (response.status !== 201) {\n throw new Error(`WHIP failed: ${response.status} ${await response.text()}`);\n }\n\n const answerSdp = await response.text();\n this._whipResourceUrl = response.headers.get('location');\n\n await this._pc.setRemoteDescription(\n new RTCSessionDescription({ type: 'answer', sdp: answerSdp })\n );\n\n this._pc.onconnectionstatechange = () => {\n const state = this._pc?.connectionState;\n if (state === 'connected') {\n this._connected = true;\n this._emit('ready');\n } else if (state === 'failed' || state === 'disconnected') {\n this._emit('error', new Error(`WHIP PeerConnection ${state}`));\n }\n };\n\n this._emit('ready');\n }\n\n // \u2500\u2500 WebSocket \u2500\u2500\n\n /** @private WS \uC5F0\uACB0 */\n _connectWebSocket() {\n this._signaling = new FuzionXSignaling({\n url: this.url,\n autoReconnect: this.autoReconnect,\n onOpen: () => this._onSignalingOpen(),\n onMessage: (msg) => this._onSignalingMessage(msg),\n onClose: (evt) => {\n this._closePeerConnection();\n this._connected = false;\n this._emit('close', evt);\n },\n onError: (err) => this._emit('error', err),\n });\n this._signaling.connect();\n }\n\n /** @private */\n async _onSignalingOpen() {\n this._signaling.sendJoin(this.peerId, this.channelId, {\n nickname: this.nickname,\n token: this.token,\n mode: this.mode,\n });\n await this._createPeerConnection();\n }\n\n /** @private */\n async _createPeerConnection() {\n this._pc = new RTCPeerConnection(this.rtcConfig);\n\n // Non-Trickle: ICE candidate\uB294 SDP\uC5D0 \uD3EC\uD568\n\n // \uB85C\uCEEC \uD2B8\uB799 \uCD94\uAC00 (\uC1A1\uCD9C)\n this._localStream.getTracks().forEach((track) => {\n this._pc.addTrack(track, this._localStream);\n });\n\n // videochat \uBAA8\uB4DC: \uC218\uC2E0 \uD2B8\uB799\uB3C4 \uC900\uBE44 (\uC591\uBC29\uD5A5)\n if (this.mode === SessionMode.VIDEOCHAT) {\n for (let i = 0; i < this._maxSlots; i++) {\n this._pc.addTransceiver('video', { direction: 'recvonly' });\n this._pc.addTransceiver('audio', { direction: 'recvonly' });\n }\n\n // \uC1A1\uCD9C \uD2B8\uB799\uC740 addTrack\uC73C\uB85C \uCD94\uAC00\uB428 \u2192 transceiver\uBC29\uD5A5\uC744 sendrecv\uB85C \uC5C5\uADF8\uB808\uC774\uB4DC\n const transceivers = this._pc.getTransceivers();\n transceivers.forEach((t) => {\n if (t.sender.track && t.direction === 'recvonly') {\n t.direction = 'sendrecv';\n }\n });\n\n let videoTrackCount = 0;\n this._pc.ontrack = (event) => {\n const track = event.track;\n if (track.kind === 'video') {\n const slotIndex = videoTrackCount++;\n let stream = event.streams[0];\n if (!stream) {\n stream = new MediaStream();\n stream.addTrack(track);\n }\n const slot = this._slots.get(slotIndex) || { slotIndex };\n slot.stream = stream;\n this._slots.set(slotIndex, slot);\n this._emit('stream', stream, slotIndex);\n }\n };\n }\n\n this._pc.onconnectionstatechange = () => {\n const state = this._pc?.connectionState;\n if (state === 'connected' && !this._connected) {\n this._connected = true;\n this._emit('ready');\n } else if (state === 'failed' || state === 'disconnected') {\n this._emit('error', new Error(`PeerConnection ${state}`));\n }\n };\n\n // Offer + H264/Opus SDP \uAC15\uC81C\n const offer = await this._pc.createOffer();\n offer.sdp = FuzionXPublisher._forceCodecs(offer.sdp);\n await this._pc.setLocalDescription(offer);\n\n // Non-Trickle: ICE gathering \uC644\uB8CC \uB300\uAE30\n await this._waitForIceGathering();\n\n const finalSdp = this._pc.localDescription?.sdp;\n if (finalSdp) {\n this._signaling.sendOffer(finalSdp);\n }\n }\n\n /** @private */\n _waitForIceGathering() {\n return new Promise((resolve) => {\n if (this._pc.iceGatheringState === 'complete') {\n return resolve();\n }\n const check = () => {\n if (this._pc?.iceGatheringState === 'complete') {\n this._pc.removeEventListener('icegatheringstatechange', check);\n resolve();\n }\n };\n this._pc.addEventListener('icegatheringstatechange', check);\n setTimeout(() => {\n if (this._pc) {\n this._pc.removeEventListener('icegatheringstatechange', check);\n }\n resolve();\n }, 150);\n });\n }\n\n /** @private */\n static _forceCodecs(sdp) {\n let lines = sdp.split('\\r\\n');\n const videoIdx = lines.findIndex((l) => l.startsWith('m=video'));\n if (videoIdx !== -1) {\n const h264Pts = [];\n const h264Scores = new Map();\n lines.forEach((l) => {\n const m = l.match(/a=rtpmap:(\\d+) H264\\/90000/);\n if (m) { h264Pts.push(m[1]); h264Scores.set(m[1], 0); }\n });\n lines.forEach((l) => {\n if (l.startsWith('a=fmtp:')) {\n const pt = l.split(' ')[0].split(':')[1];\n if (h264Scores.has(pt)) {\n if (l.includes('profile-level-id=42e01f')) h264Scores.set(pt, 100);\n else if (l.includes('profile-level-id=42001f')) h264Scores.set(pt, 80);\n if (l.includes('packetization-mode=1')) h264Scores.set(pt, (h264Scores.get(pt) || 0) + 10);\n }\n }\n });\n if (h264Pts.length > 0) {\n h264Pts.sort((a, b) => h264Scores.get(b) - h264Scores.get(a));\n const parts = lines[videoIdx].split(' ');\n const otherPts = parts.slice(3).filter((pt) => !h264Pts.includes(pt));\n lines[videoIdx] = [...parts.slice(0, 3), ...h264Pts, ...otherPts].join(' ');\n }\n }\n const audioIdx = lines.findIndex((l) => l.startsWith('m=audio'));\n if (audioIdx !== -1) {\n const opusPts = [];\n lines.forEach((l) => {\n const m = l.match(/a=rtpmap:(\\d+) opus\\/48000/);\n if (m) opusPts.push(m[1]);\n });\n if (opusPts.length > 0) {\n const parts = lines[audioIdx].split(' ');\n const otherPts = parts.slice(3).filter((pt) => !opusPts.includes(pt));\n lines[audioIdx] = [...parts.slice(0, 3), ...opusPts, ...otherPts].join(' ');\n }\n }\n return lines.join('\\r\\n');\n }\n\n /** @private */\n _onSignalingMessage(msg) {\n switch (msg.type) {\n case SignalType.ANSWER:\n this._handleAnswer(msg);\n break;\n case SignalType.CANDIDATE:\n this._handleCandidate(msg);\n break;\n case SignalType.SLOT_INFO:\n this._handleSlotInfo(msg);\n break;\n case SignalType.CHAT:\n this._emit('chat', {\n peerId: msg.peer_id,\n nickname: msg.nickname,\n text: msg.text,\n });\n break;\n case SignalType.ERROR:\n this._emit('error', new Error(msg.message));\n break;\n }\n }\n\n /** @private */\n async _handleAnswer(msg) {\n if (!this._pc) return;\n try {\n await this._pc.setRemoteDescription(\n new RTCSessionDescription({ type: 'answer', sdp: msg.sdp })\n );\n for (const c of this._candidateQueue) {\n await this._pc.addIceCandidate(c);\n }\n this._candidateQueue = [];\n } catch (e) {\n this._emit('error', e);\n }\n }\n\n /** @private */\n async _handleCandidate(msg) {\n const candidate = new RTCIceCandidate({\n candidate: msg.candidate,\n sdpMid: msg.sdp_mid,\n sdpMLineIndex: msg.sdp_m_line_index,\n });\n if (this._pc && this._pc.remoteDescription) {\n try { await this._pc.addIceCandidate(candidate); } catch (e) { /* ignore */ }\n } else {\n this._candidateQueue.push(candidate);\n }\n }\n\n /** @private */\n _handleSlotInfo(msg) {\n // \uC790\uAE30 \uC790\uC2E0\uC758 SlotInfo\uB294 \uBB34\uC2DC (\uC11C\uBC84\uB294 RTP\uB9CC self-skip, SlotInfo\uB294 \uBCF4\uB0C4)\n if (msg.sender_id === this.peerId) return;\n\n const slotIndex = parseInt(msg.stream_id.replace('stream_', ''), 10);\n if (!msg.nickname || msg.nickname === '') {\n // stream \uCC38\uC870\uB294 \uBCF4\uC874 (WebRTC \uD2B8\uB799\uC740 PC \uC218\uBA85\uACFC \uB3D9\uC77C)\n const existing = this._slots.get(slotIndex);\n if (existing) {\n this._slots.set(slotIndex, { slotIndex, stream: existing.stream });\n }\n this._emit('slot_remove', { slotIndex, senderId: msg.sender_id });\n return;\n }\n const existing = this._slots.get(slotIndex) || {};\n this._slots.set(slotIndex, {\n ...existing,\n slotIndex,\n streamId: msg.stream_id,\n nickname: msg.nickname,\n senderId: msg.sender_id,\n });\n this._emit('slot', this._slots.get(slotIndex));\n }\n\n /** @private */\n _closePeerConnection() {\n if (this._pc) {\n this._pc.close();\n this._pc = null;\n }\n this._candidateQueue = [];\n }\n}\n"],
5
+ "mappings": "obAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,WAAAE,EAAA,wBAAAC,EAAA,qBAAAC,EAAA,qBAAAC,EAAA,kBAAAC,EAAA,cAAAC,EAAA,cAAAC,EAAA,gBAAAC,EAAA,eAAAC,ICKO,IAAMC,EAAsB,CACjC,CAAE,KAAM,8BAA+B,EACvC,CAAE,KAAM,+BAAgC,CAC1C,EAGaC,EAAa,CACxB,KAAM,OACN,MAAO,QACP,OAAQ,SACR,UAAW,YACX,IAAK,MACL,MAAO,QACP,UAAW,YACX,KAAM,OACN,MAAO,OACT,EAGaC,EAAc,CACzB,UAAW,YACX,UAAW,WACb,EAGaC,EAAY,CACvB,CAACD,EAAY,SAAS,EAAG,EACzB,CAACA,EAAY,SAAS,EAAG,CAC3B,EAGaE,EAAY,CACvB,YAAa,EACb,cAAe,IACf,aAAc,GAChB,EAGaC,EAAQ,CACnB,WAAY,aACZ,WAAY,aACZ,YAAa,IACb,YAAa,IACf,ECvCO,IAAMC,EAAN,KAAuB,CAU5B,YAAYC,EAAM,CAChB,KAAK,IAAMA,EAAK,IAChB,KAAK,UAAYA,EAAK,YAAc,IAAM,CAAC,GAC3C,KAAK,OAASA,EAAK,SAAW,IAAM,CAAC,GACrC,KAAK,QAAUA,EAAK,UAAY,IAAM,CAAC,GACvC,KAAK,QAAUA,EAAK,UAAY,IAAM,CAAC,GACvC,KAAK,cAAgBA,EAAK,gBAAkB,GAG5C,KAAK,IAAM,KACX,KAAK,YAAc,EACnB,KAAK,gBAAkB,KACvB,KAAK,kBAAoB,EAC3B,CAGA,SAAU,CACR,KAAK,kBAAoB,GACzB,KAAK,WAAW,CAClB,CAGA,YAAa,CACX,GAAI,CACF,KAAK,IAAM,IAAI,UAAU,KAAK,GAAG,CACnC,OAAS,EAAG,CACV,KAAK,QAAQ,CAAC,EACd,KAAK,mBAAmB,EACxB,MACF,CAEA,KAAK,IAAI,OAAS,IAAM,CACtB,KAAK,YAAc,EACnB,KAAK,OAAO,CACd,EAEA,KAAK,IAAI,UAAaC,GAAU,CAC9B,GAAI,CACF,IAAMC,EAAM,KAAK,MAAMD,EAAM,IAAI,EACjC,KAAK,UAAUC,CAAG,CACpB,MAAY,CACV,QAAQ,KAAK,0BAA2BD,EAAM,IAAI,CACpD,CACF,EAEA,KAAK,IAAI,QAAWA,GAAU,CAC5B,KAAK,QAAQA,CAAK,EACd,CAAC,KAAK,mBAAqB,KAAK,eAClC,KAAK,mBAAmB,CAE5B,EAEA,KAAK,IAAI,QAAWA,GAAU,CAC5B,KAAK,QAAQA,CAAK,CACpB,CACF,CAGA,KAAKC,EAAK,CACR,OAAI,KAAK,KAAO,KAAK,IAAI,aAAe,UAAU,MAChD,KAAK,IAAI,KAAK,KAAK,UAAUA,CAAG,CAAC,EAC1B,IAEF,EACT,CAKA,SAASC,EAAQC,EAAWJ,EAAO,CAAC,EAAG,CACrC,OAAO,KAAK,KAAK,CACf,KAAMK,EAAW,KACjB,QAASF,EACT,WAAYC,EACZ,SAAUJ,EAAK,UAAY,KAC3B,MAAOA,EAAK,OAAS,KACrB,KAAMA,EAAK,MAAQ,IACrB,CAAC,CACH,CAGA,UAAUM,EAAK,CACb,OAAO,KAAK,KAAK,CAAE,KAAMD,EAAW,MAAO,IAAAC,CAAI,CAAC,CAClD,CAGA,WAAWA,EAAK,CACd,OAAO,KAAK,KAAK,CAAE,KAAMD,EAAW,OAAQ,IAAAC,CAAI,CAAC,CACnD,CAGA,cAAcC,EAAW,CACvB,OAAO,KAAK,KAAK,CACf,KAAMF,EAAW,UACjB,UAAWE,EAAU,UACrB,QAASA,EAAU,OACnB,iBAAkBA,EAAU,aAC9B,CAAC,CACH,CAGA,SAASC,EAAMC,EAAU,CACvB,OAAO,KAAK,KAAK,CACf,KAAMJ,EAAW,KACjB,KAAAG,EACA,SAAUC,GAAY,KACtB,QAAS,IACX,CAAC,CACH,CAGA,SAAU,CACR,OAAO,KAAK,KAAK,CAAE,KAAMJ,EAAW,GAAI,CAAC,CAC3C,CAGA,WAAY,CACV,OAAO,KAAK,KAAK,CAAE,KAAMA,EAAW,KAAM,CAAC,CAC7C,CAGA,YAAa,CACX,KAAK,kBAAoB,GACzB,aAAa,KAAK,eAAe,EAC7B,KAAK,MACP,KAAK,IAAI,MAAM,EACf,KAAK,IAAM,KAEf,CAGA,IAAI,WAAY,CACd,OAAO,KAAK,KAAO,KAAK,IAAI,aAAe,UAAU,IACvD,CAGA,oBAAqB,CACnB,GAAI,KAAK,aAAeK,EAAU,YAAa,CAC7C,QAAQ,MAAM,0CAA0C,EACxD,KAAK,QAAQ,IAAI,MAAM,uBAAuB,CAAC,EAC/C,MACF,CACA,IAAMC,EAAQ,KAAK,IACjBD,EAAU,cAAgB,KAAK,IAAI,EAAG,KAAK,WAAW,EACtDA,EAAU,YACZ,EACA,KAAK,cACL,QAAQ,IAAI,6BAA6BC,CAAK,OAAO,KAAK,WAAW,IAAID,EAAU,WAAW,GAAG,EACjG,KAAK,gBAAkB,WAAW,IAAM,KAAK,WAAW,EAAGC,CAAK,CAClE,CACF,ECpJO,IAAMC,EAAN,MAAMC,CAAc,CAazB,YAAYC,EAAM,CAChB,KAAK,IAAMA,EAAK,KAAO,KACvB,KAAK,OAASA,EAAK,QAAU,KAC7B,KAAK,UAAYA,EAAK,UACtB,KAAK,KAAOA,EAAK,MAAQC,EAAY,UACrC,KAAK,SAAWD,EAAK,UAAY,KACjC,KAAK,MAAQA,EAAK,OAAS,KAC3B,KAAK,OAASA,EAAK,QAAU,UAAU,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,EAAG,EAAE,CAAC,GAC9E,KAAK,cAAgBA,EAAK,gBAAkB,GAE5C,KAAK,UAAYA,EAAK,WAAa,CACjC,WAAYE,EACZ,aAAc,aACd,cAAe,SACjB,EAGA,KAAK,WAAa,KAClB,KAAK,IAAM,KACX,KAAK,WAAa,CAAC,EACnB,KAAK,OAAS,IAAI,IAClB,KAAK,UAAYC,EAAU,KAAK,IAAI,GAAK,EACzC,KAAK,gBAAkB,CAAC,EACxB,KAAK,WAAa,EACpB,CASA,GAAGC,EAAOC,EAAS,CACjB,OAAK,KAAK,WAAWD,CAAK,IAAG,KAAK,WAAWA,CAAK,EAAI,CAAC,GACvD,KAAK,WAAWA,CAAK,EAAE,KAAKC,CAAO,EAC5B,IACT,CAGA,MAAMD,KAAUE,EAAM,EACnB,KAAK,WAAWF,CAAK,GAAK,CAAC,GAAG,QAASG,GAAOA,EAAG,GAAGD,CAAI,CAAC,CAC5D,CAKA,MAAM,SAAU,CAEd,GAAI,CAAC,KAAK,KAAO,KAAK,OACpB,GAAI,CACF,IAAME,EAAM,MAAM,MAAM,GAAG,KAAK,MAAM,iBAAiB,KAAK,SAAS,EAAE,EACvE,GAAI,CAACA,EAAI,GAAI,MAAM,IAAI,MAAM,sBAAsB,KAAK,SAAS,EAAE,EACnE,IAAMC,EAAO,MAAMD,EAAI,KAAK,EAE5B,GAAIC,EAAK,OAAQ,CAEf,IAAMC,EAAW,KAAK,OAAO,WAAW,OAAO,EAC/C,KAAK,IAAMD,EAAK,OAAO,QAAQ,WAAYC,EAAW,OAAS,KAAK,CACtE,KAAO,CAEL,IAAMC,EADW,KAAK,OAAO,WAAW,OAAO,EACpB,MAAQ,KACnC,KAAK,IAAM,GAAGA,CAAO,MAAMF,EAAK,QAAQ,IAAIA,EAAK,WAAW,EAC9D,CACF,OAAS,EAAG,CACV,KAAK,MAAM,QAAS,CAAC,EACrB,MACF,CAGF,GAAI,CAAC,KAAK,IAAK,CACb,KAAK,MAAM,QAAS,IAAI,MAAM,4EAA0B,CAAC,EACzD,MACF,CAEA,KAAK,WAAa,IAAIG,EAAiB,CACrC,IAAK,KAAK,IACV,cAAe,KAAK,cACpB,OAAQ,IAAM,KAAK,iBAAiB,EACpC,UAAYC,GAAQ,KAAK,oBAAoBA,CAAG,EAChD,QAAUC,GAAQ,KAAK,kBAAkBA,CAAG,EAC5C,QAAUC,GAAQ,KAAK,MAAM,QAASA,CAAG,CAC3C,CAAC,EACD,KAAK,WAAW,QAAQ,EAGxB,KAAK,qBAAuB,IAAM,CAC5B,KAAK,YAAc,KAAK,WAAW,WACrC,KAAK,WAAW,UAAU,EAE5B,KAAK,qBAAqB,CAC5B,EACA,OAAO,iBAAiB,eAAgB,KAAK,oBAAoB,CACnE,CAGA,MAAM,YAAa,CAEb,KAAK,uBACP,OAAO,oBAAoB,eAAgB,KAAK,oBAAoB,EACpE,KAAK,qBAAuB,MAG1B,KAAK,aACP,KAAK,WAAW,UAAU,EAE1B,MAAM,IAAI,QAAQC,GAAK,WAAWA,EAAG,GAAG,CAAC,EACzC,KAAK,WAAW,WAAW,GAE7B,KAAK,qBAAqB,EAC1B,KAAK,OAAO,MAAM,EAClB,KAAK,WAAa,EACpB,CAGA,KAAKC,EAAM,CACL,KAAK,YACP,KAAK,WAAW,SAASA,EAAM,KAAK,QAAQ,CAEhD,CAGA,iBAAkB,CACZ,KAAK,YACP,KAAK,WAAW,QAAQ,CAE5B,CAGA,IAAI,OAAQ,CACV,OAAO,KAAK,MACd,CAKA,kBAAmB,CACjB,KAAK,WAAW,SAAS,KAAK,OAAQ,KAAK,UAAW,CACpD,SAAU,KAAK,SACf,MAAO,KAAK,MACZ,KAAM,KAAK,IACb,CAAC,EAED,KAAK,sBAAsB,EAAE,MAAO,GAAM,KAAK,MAAM,QAAS,CAAC,CAAC,CAClE,CAGA,oBAAoBJ,EAAK,CACvB,OAAQA,EAAI,KAAM,CAChB,KAAKK,EAAW,OACd,KAAK,cAAcL,CAAG,EACtB,MACF,KAAKK,EAAW,UACd,KAAK,iBAAiBL,CAAG,EACzB,MACF,KAAKK,EAAW,UACd,KAAK,gBAAgBL,CAAG,EACxB,MACF,KAAKK,EAAW,KACd,KAAK,MAAM,OAAQ,CACjB,OAAQL,EAAI,QACZ,SAAUA,EAAI,SACd,KAAMA,EAAI,IACZ,CAAC,EACD,MACF,KAAKK,EAAW,MACd,KAAK,MAAM,QAAS,IAAI,MAAML,EAAI,OAAO,CAAC,EAC1C,KACJ,CACF,CAGA,kBAAkBC,EAAK,CACrB,KAAK,qBAAqB,EAC1B,KAAK,WAAa,GAClB,KAAK,MAAM,QAASA,CAAG,CACzB,CAGA,MAAM,cAAcD,EAAK,CACvB,GAAK,KAAK,IACV,GAAI,CACF,MAAM,KAAK,IAAI,qBACb,IAAI,sBAAsB,CAAE,KAAM,SAAU,IAAKA,EAAI,GAAI,CAAC,CAC5D,EAEA,QAAWM,KAAK,KAAK,gBACnB,MAAM,KAAK,IAAI,gBAAgBA,CAAC,EAElC,KAAK,gBAAkB,CAAC,CAC1B,OAASC,EAAG,CACV,KAAK,MAAM,QAASA,CAAC,CACvB,CACF,CAGA,MAAM,iBAAiBP,EAAK,CAC1B,IAAMQ,EAAY,IAAI,gBAAgB,CACpC,UAAWR,EAAI,UACf,OAAQA,EAAI,QACZ,cAAeA,EAAI,gBACrB,CAAC,EACD,GAAI,KAAK,KAAO,KAAK,IAAI,kBACvB,GAAI,CACF,MAAM,KAAK,IAAI,gBAAgBQ,CAAS,CAC1C,OAASD,EAAG,CACV,QAAQ,KAAK,iCAAkCA,CAAC,CAClD,MAEA,KAAK,gBAAgB,KAAKC,CAAS,CAEvC,CAGA,gBAAgBR,EAAK,CACnB,IAAMS,EAAY,SAAST,EAAI,UAAU,QAAQ,UAAW,EAAE,EAAG,EAAE,EAGnE,GAAI,CAACA,EAAI,UAAYA,EAAI,WAAa,GAAI,CACxC,IAAMU,EAAW,KAAK,OAAO,IAAID,CAAS,EACtCC,GACF,KAAK,OAAO,IAAID,EAAW,CAAE,UAAAA,EAAW,OAAQC,EAAS,MAAO,CAAC,EAEnE,KAAK,MAAM,cAAe,CAAE,UAAAD,EAAW,SAAUT,EAAI,SAAU,CAAC,EAChE,MACF,CAEA,IAAMU,EAAW,KAAK,OAAO,IAAID,CAAS,GAAK,CAAC,EAChD,KAAK,OAAO,IAAIA,EAAW,CACzB,GAAGC,EACH,UAAAD,EACA,SAAUT,EAAI,UACd,SAAUA,EAAI,SACd,SAAUA,EAAI,SAChB,CAAC,EACD,KAAK,MAAM,OAAQ,KAAK,OAAO,IAAIS,CAAS,CAAC,CAC/C,CAUA,MAAM,uBAAwB,CAC5B,KAAK,IAAM,IAAI,kBAAkB,KAAK,SAAS,EAM/C,IAAIE,EAAkB,EACtB,KAAK,IAAI,QAAWpB,GAAU,CAC5B,IAAMqB,EAAQrB,EAAM,MAEpB,GAAIqB,EAAM,OAAS,QAAS,CAC1B,IAAMH,EAAYE,IAEdE,EAAStB,EAAM,QAAQ,CAAC,EACvBsB,IACHA,EAAS,IAAI,YACbA,EAAO,SAASD,CAAK,GAGvB,IAAME,EAAO,KAAK,OAAO,IAAIL,CAAS,GAAK,CAAE,UAAAA,CAAU,EACvDK,EAAK,OAASD,EACd,KAAK,OAAO,IAAIJ,EAAWK,CAAI,EAE/B,KAAK,MAAM,SAAUD,EAAQJ,CAAS,EAEjC,KAAK,aACR,KAAK,WAAa,GAClB,KAAK,MAAM,WAAW,EAGlB,KAAK,YACP,KAAK,WAAW,QAAQ,EAG9B,CACF,EAEA,KAAK,IAAI,wBAA0B,IAAM,CACvC,IAAMM,EAAQ,KAAK,KAAK,iBACpBA,IAAU,UAAYA,IAAU,iBAClC,KAAK,MAAM,QAAS,IAAI,MAAM,kBAAkBA,CAAK,EAAE,CAAC,CAE5D,EAGA,QAASC,EAAI,EAAGA,EAAI,KAAK,UAAWA,IAClC,KAAK,IAAI,eAAe,QAAS,CAAE,UAAW,UAAW,CAAC,EAC1D,KAAK,IAAI,eAAe,QAAS,CAAE,UAAW,UAAW,CAAC,EAI5D,IAAMC,EAAQ,MAAM,KAAK,IAAI,YAAY,EACzCA,EAAM,IAAM/B,EAAc,aAAa+B,EAAM,GAAG,EAChD,MAAM,KAAK,IAAI,oBAAoBA,CAAK,EAGxC,MAAM,KAAK,qBAAqB,EAGhC,IAAMC,EAAW,KAAK,IAAI,kBAAkB,IACxCA,GACF,KAAK,WAAW,UAAUA,CAAQ,CAEtC,CAMA,sBAAuB,CACrB,OAAO,IAAI,QAASC,GAAY,CAC9B,GAAI,KAAK,IAAI,oBAAsB,WACjC,OAAOA,EAAQ,EAEjB,IAAMC,EAAQ,IAAM,CACd,KAAK,KAAK,oBAAsB,aAClC,KAAK,IAAI,oBAAoB,0BAA2BA,CAAK,EAC7DD,EAAQ,EAEZ,EACA,KAAK,IAAI,iBAAiB,0BAA2BC,CAAK,EAE1D,WAAW,IAAM,CACX,KAAK,KACP,KAAK,IAAI,oBAAoB,0BAA2BA,CAAK,EAE/DD,EAAQ,CACV,EAAG,GAAG,CACR,CAAC,CACH,CAMA,OAAO,aAAaE,EAAK,CACvB,IAAIC,EAAQD,EAAI,MAAM;AAAA,CAAM,EAGtBE,EAAWD,EAAM,UAAWE,GAAMA,EAAE,WAAW,SAAS,CAAC,EAC/D,GAAID,IAAa,GAAI,CACnB,IAAME,EAAU,CAAC,EACXC,EAAa,IAAI,IAkBvB,GAjBAJ,EAAM,QAASE,GAAM,CACnB,IAAMG,EAAIH,EAAE,MAAM,4BAA4B,EAC1CG,IACFF,EAAQ,KAAKE,EAAE,CAAC,CAAC,EACjBD,EAAW,IAAIC,EAAE,CAAC,EAAG,CAAC,EAE1B,CAAC,EACDL,EAAM,QAASE,GAAM,CACnB,GAAIA,EAAE,WAAW,SAAS,EAAG,CAC3B,IAAMI,EAAKJ,EAAE,MAAM,GAAG,EAAE,CAAC,EAAE,MAAM,GAAG,EAAE,CAAC,EACnCE,EAAW,IAAIE,CAAE,IACfJ,EAAE,SAAS,yBAAyB,EAAGE,EAAW,IAAIE,EAAI,GAAG,EACxDJ,EAAE,SAAS,yBAAyB,GAAGE,EAAW,IAAIE,EAAI,EAAE,EACjEJ,EAAE,SAAS,sBAAsB,GAAGE,EAAW,IAAIE,GAAKF,EAAW,IAAIE,CAAE,GAAK,GAAK,EAAE,EAE7F,CACF,CAAC,EACGH,EAAQ,OAAS,EAAG,CACtBA,EAAQ,KAAK,CAACI,EAAGC,IAAMJ,EAAW,IAAII,CAAC,EAAIJ,EAAW,IAAIG,CAAC,CAAC,EAC5D,IAAME,EAAQT,EAAMC,CAAQ,EAAE,MAAM,GAAG,EACjCS,EAAWD,EAAM,MAAM,CAAC,EAAE,OAAQH,GAAO,CAACH,EAAQ,SAASG,CAAE,CAAC,EACpEN,EAAMC,CAAQ,EAAI,CAAC,GAAGQ,EAAM,MAAM,EAAG,CAAC,EAAG,GAAGN,EAAS,GAAGO,CAAQ,EAAE,KAAK,GAAG,CAC5E,CACF,CAGA,IAAMC,EAAWX,EAAM,UAAWE,GAAMA,EAAE,WAAW,SAAS,CAAC,EAC/D,GAAIS,IAAa,GAAI,CACnB,IAAMC,EAAU,CAAC,EAKjB,GAJAZ,EAAM,QAASE,GAAM,CACnB,IAAMG,EAAIH,EAAE,MAAM,4BAA4B,EAC1CG,GAAGO,EAAQ,KAAKP,EAAE,CAAC,CAAC,CAC1B,CAAC,EACGO,EAAQ,OAAS,EAAG,CACtB,IAAMH,EAAQT,EAAMW,CAAQ,EAAE,MAAM,GAAG,EACjCD,EAAWD,EAAM,MAAM,CAAC,EAAE,OAAQH,GAAO,CAACM,EAAQ,SAASN,CAAE,CAAC,EACpEN,EAAMW,CAAQ,EAAI,CAAC,GAAGF,EAAM,MAAM,EAAG,CAAC,EAAG,GAAGG,EAAS,GAAGF,CAAQ,EAAE,KAAK,GAAG,CAC5E,CACF,CAEA,OAAOV,EAAM,KAAK;AAAA,CAAM,CAC1B,CAGA,sBAAuB,CACjB,KAAK,MACP,KAAK,IAAI,MAAM,EACf,KAAK,IAAM,MAEb,KAAK,gBAAkB,CAAC,CAC1B,CAEF,ECxZO,IAAMa,EAAN,MAAMC,CAAiB,CAe5B,YAAYC,EAAM,CAEhB,KAAK,QAAUA,EAAK,SAAW,KAC/B,KAAK,IAAMA,EAAK,KAAO,KACvB,KAAK,OAASA,EAAK,QAAU,KAC7B,KAAK,UAAYA,EAAK,WAAa,KACnC,KAAK,KAAOA,EAAK,MAAQC,EAAY,UACrC,KAAK,SAAWD,EAAK,UAAY,KACjC,KAAK,MAAQA,EAAK,OAAS,KAC3B,KAAK,OAASA,EAAK,QAAU,OAAO,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,EAAG,EAAE,CAAC,GAC3E,KAAK,cAAgBA,EAAK,gBAAkB,GAE5C,KAAK,iBAAmBA,EAAK,OAAS,CAAE,MAAO,GAAM,MAAO,EAAK,EACjE,KAAK,gBAAkBA,EAAK,QAAU,KAEtC,KAAK,UAAYA,EAAK,WAAa,CACjC,WAAYE,EACZ,aAAc,aACd,cAAe,SACjB,EAGA,KAAK,WAAa,KAClB,KAAK,IAAM,KACX,KAAK,aAAe,KACpB,KAAK,WAAa,CAAC,EACnB,KAAK,gBAAkB,CAAC,EACxB,KAAK,iBAAmB,KACxB,KAAK,UAAYC,EAAU,KAAK,IAAI,GAAK,EACzC,KAAK,OAAS,IAAI,IAClB,KAAK,WAAa,EACpB,CAIA,GAAGC,EAAOC,EAAS,CACjB,OAAK,KAAK,WAAWD,CAAK,IAAG,KAAK,WAAWA,CAAK,EAAI,CAAC,GACvD,KAAK,WAAWA,CAAK,EAAE,KAAKC,CAAO,EAC5B,IACT,CAGA,MAAMD,KAAUE,EAAM,EACnB,KAAK,WAAWF,CAAK,GAAK,CAAC,GAAG,QAASG,GAAOA,EAAG,GAAGD,CAAI,CAAC,CAC5D,CAKA,MAAM,SAAU,CACd,GAAI,CAEF,GAAI,CAAC,KAAK,KAAO,CAAC,KAAK,SAAW,KAAK,QAAU,KAAK,UAAW,CAG/D,IAAME,EAAY,MAAM,MAAM,GAAG,KAAK,MAAM,gBAAiB,CAC3D,OAAQ,OACR,QAAS,CAAE,eAAgB,kBAAmB,EAC9C,KAAM,KAAK,UAAU,CACnB,WAAY,KAAK,UACjB,YAAa,QACf,CAAC,CACH,CAAC,EAEGC,EACJ,GAAID,EAAU,SAAW,IAAK,CAG5B,IAAME,EAAS,MAAM,MAAM,GAAG,KAAK,MAAM,iBAAiB,KAAK,SAAS,EAAE,EAC1E,GAAI,CAACA,EAAO,GAAI,MAAM,IAAI,MAAM,sBAAsB,KAAK,SAAS,EAAE,EACtED,EAAO,MAAMC,EAAO,KAAK,CAC3B,SAAYF,EAAU,GAGpBC,EAAO,MAAMD,EAAU,KAAK,MAF5B,OAAM,IAAI,MAAM,6BAA6B,KAAK,SAAS,EAAE,EAK/D,GAAIC,EAAK,OAAQ,CACf,IAAME,EAAW,KAAK,OAAO,WAAW,OAAO,EAC/C,KAAK,IAAMF,EAAK,OAAO,QAAQ,WAAYE,EAAW,OAAS,KAAK,CACtE,KAAO,CAEL,IAAMC,EADW,KAAK,OAAO,WAAW,OAAO,EACpB,MAAQ,KACnC,KAAK,IAAM,GAAGA,CAAO,MAAMH,EAAK,QAAQ,IAAIA,EAAK,WAAW,EAC9D,CACF,CAIA,GAFA,MAAM,KAAK,cAAc,EAErB,KAAK,QACP,MAAM,KAAK,aAAa,UACf,KAAK,IACd,KAAK,kBAAkB,MAEvB,OAAM,IAAI,MAAM,0GAAyC,EAI3D,KAAK,qBAAuB,IAAM,CAC5B,KAAK,YAAc,KAAK,WAAW,WACrC,KAAK,WAAW,UAAU,EAE5B,KAAK,qBAAqB,EAC1B,KAAK,WAAW,CAClB,EACA,OAAO,iBAAiB,eAAgB,KAAK,oBAAoB,CACnE,OAAS,EAAG,CACV,KAAK,MAAM,QAAS,CAAC,CACvB,CACF,CAGA,MAAM,YAAa,CAOjB,GALI,KAAK,uBACP,OAAO,oBAAoB,eAAgB,KAAK,oBAAoB,EACpE,KAAK,qBAAuB,MAG1B,KAAK,SAAW,KAAK,iBAAkB,CAEzC,GAAI,CACF,IAAMI,EAAU,IAAI,IAAI,KAAK,OAAO,EAAE,OACtC,MAAM,MAAM,GAAGA,CAAO,GAAG,KAAK,gBAAgB,GAAI,CAAE,OAAQ,QAAS,CAAC,CACxE,OAAS,EAAG,CACV,QAAQ,KAAK,+BAAgC,CAAC,CAChD,CACA,KAAK,iBAAmB,IAC1B,CAEI,KAAK,aACP,KAAK,WAAW,UAAU,EAE1B,MAAM,IAAI,QAAQC,GAAK,WAAWA,EAAG,GAAG,CAAC,EACzC,KAAK,WAAW,WAAW,GAG7B,KAAK,qBAAqB,EAC1B,KAAK,WAAW,EAChB,KAAK,WAAa,EACpB,CAGA,KAAKC,EAAM,CACL,KAAK,YACP,KAAK,WAAW,SAASA,EAAM,KAAK,QAAQ,CAEhD,CAGA,IAAI,aAAc,CAChB,OAAO,KAAK,YACd,CAGA,IAAI,OAAQ,CACV,OAAO,KAAK,MACd,CAKA,MAAM,eAAgB,CAChB,KAAK,gBACP,KAAK,aAAe,KAAK,gBAEzB,KAAK,aAAe,MAAM,UAAU,aAAa,aAAa,KAAK,gBAAgB,EAErF,KAAK,MAAM,QAAS,KAAK,YAAY,CACvC,CAGA,YAAa,CACP,KAAK,cAAgB,CAAC,KAAK,iBAC7B,KAAK,aAAa,UAAU,EAAE,QAASC,GAAMA,EAAE,KAAK,CAAC,EAEvD,KAAK,aAAe,IACtB,CAKA,MAAM,cAAe,CACnB,KAAK,IAAM,IAAI,kBAAkB,KAAK,SAAS,EAG/C,KAAK,aAAa,UAAU,EAAE,QAASC,GAAU,CAC/C,KAAK,IAAI,SAASA,EAAO,KAAK,YAAY,CAC5C,CAAC,EAGD,IAAMC,EAAQ,MAAM,KAAK,IAAI,YAAY,EACzCA,EAAM,IAAMnB,EAAiB,aAAamB,EAAM,GAAG,EACnD,MAAM,KAAK,IAAI,oBAAoBA,CAAK,EAGxC,MAAM,IAAI,QAASC,GAAY,CACzB,KAAK,IAAI,oBAAsB,WACjCA,EAAQ,GAER,KAAK,IAAI,0BAA4B,IAAM,CACrC,KAAK,IAAI,oBAAsB,YAAYA,EAAQ,CACzD,EAEA,WAAWA,EAAS,GAAG,EAE3B,CAAC,EAED,IAAMC,EAAY,KAAK,IAAI,iBAEvBC,EAAM,KAAK,QACX,KAAK,OAAS,CAACA,EAAI,SAAS,QAAQ,IACtCA,IAAQA,EAAI,SAAS,GAAG,EAAI,IAAM,KAAO,SAAS,KAAK,KAAK,IAG9D,IAAMC,EAAW,MAAM,MAAMD,EAAK,CAChC,OAAQ,OACR,QAAS,CAAE,eAAgB,iBAAkB,EAC7C,KAAMD,EAAU,GAClB,CAAC,EAED,GAAIE,EAAS,SAAW,IACtB,MAAM,IAAI,MAAM,gBAAgBA,EAAS,MAAM,IAAI,MAAMA,EAAS,KAAK,CAAC,EAAE,EAG5E,IAAMC,EAAY,MAAMD,EAAS,KAAK,EACtC,KAAK,iBAAmBA,EAAS,QAAQ,IAAI,UAAU,EAEvD,MAAM,KAAK,IAAI,qBACb,IAAI,sBAAsB,CAAE,KAAM,SAAU,IAAKC,CAAU,CAAC,CAC9D,EAEA,KAAK,IAAI,wBAA0B,IAAM,CACvC,IAAMC,EAAQ,KAAK,KAAK,gBACpBA,IAAU,aACZ,KAAK,WAAa,GAClB,KAAK,MAAM,OAAO,IACTA,IAAU,UAAYA,IAAU,iBACzC,KAAK,MAAM,QAAS,IAAI,MAAM,uBAAuBA,CAAK,EAAE,CAAC,CAEjE,EAEA,KAAK,MAAM,OAAO,CACpB,CAKA,mBAAoB,CAClB,KAAK,WAAa,IAAIC,EAAiB,CACrC,IAAK,KAAK,IACV,cAAe,KAAK,cACpB,OAAQ,IAAM,KAAK,iBAAiB,EACpC,UAAYC,GAAQ,KAAK,oBAAoBA,CAAG,EAChD,QAAUC,GAAQ,CAChB,KAAK,qBAAqB,EAC1B,KAAK,WAAa,GAClB,KAAK,MAAM,QAASA,CAAG,CACzB,EACA,QAAUC,GAAQ,KAAK,MAAM,QAASA,CAAG,CAC3C,CAAC,EACD,KAAK,WAAW,QAAQ,CAC1B,CAGA,MAAM,kBAAmB,CACvB,KAAK,WAAW,SAAS,KAAK,OAAQ,KAAK,UAAW,CACpD,SAAU,KAAK,SACf,MAAO,KAAK,MACZ,KAAM,KAAK,IACb,CAAC,EACD,MAAM,KAAK,sBAAsB,CACnC,CAGA,MAAM,uBAAwB,CAW5B,GAVA,KAAK,IAAM,IAAI,kBAAkB,KAAK,SAAS,EAK/C,KAAK,aAAa,UAAU,EAAE,QAASX,GAAU,CAC/C,KAAK,IAAI,SAASA,EAAO,KAAK,YAAY,CAC5C,CAAC,EAGG,KAAK,OAAShB,EAAY,UAAW,CACvC,QAAS4B,EAAI,EAAGA,EAAI,KAAK,UAAWA,IAClC,KAAK,IAAI,eAAe,QAAS,CAAE,UAAW,UAAW,CAAC,EAC1D,KAAK,IAAI,eAAe,QAAS,CAAE,UAAW,UAAW,CAAC,EAIvC,KAAK,IAAI,gBAAgB,EACjC,QAASb,GAAM,CACtBA,EAAE,OAAO,OAASA,EAAE,YAAc,aACpCA,EAAE,UAAY,WAElB,CAAC,EAED,IAAIc,EAAkB,EACtB,KAAK,IAAI,QAAW1B,GAAU,CAC5B,IAAMa,EAAQb,EAAM,MACpB,GAAIa,EAAM,OAAS,QAAS,CAC1B,IAAMc,EAAYD,IACdE,EAAS5B,EAAM,QAAQ,CAAC,EACvB4B,IACHA,EAAS,IAAI,YACbA,EAAO,SAASf,CAAK,GAEvB,IAAMgB,EAAO,KAAK,OAAO,IAAIF,CAAS,GAAK,CAAE,UAAAA,CAAU,EACvDE,EAAK,OAASD,EACd,KAAK,OAAO,IAAID,EAAWE,CAAI,EAC/B,KAAK,MAAM,SAAUD,EAAQD,CAAS,CACxC,CACF,CACF,CAEA,KAAK,IAAI,wBAA0B,IAAM,CACvC,IAAMP,EAAQ,KAAK,KAAK,gBACpBA,IAAU,aAAe,CAAC,KAAK,YACjC,KAAK,WAAa,GAClB,KAAK,MAAM,OAAO,IACTA,IAAU,UAAYA,IAAU,iBACzC,KAAK,MAAM,QAAS,IAAI,MAAM,kBAAkBA,CAAK,EAAE,CAAC,CAE5D,EAGA,IAAMN,EAAQ,MAAM,KAAK,IAAI,YAAY,EACzCA,EAAM,IAAMnB,EAAiB,aAAamB,EAAM,GAAG,EACnD,MAAM,KAAK,IAAI,oBAAoBA,CAAK,EAGxC,MAAM,KAAK,qBAAqB,EAEhC,IAAMgB,EAAW,KAAK,IAAI,kBAAkB,IACxCA,GACF,KAAK,WAAW,UAAUA,CAAQ,CAEtC,CAGA,sBAAuB,CACrB,OAAO,IAAI,QAASf,GAAY,CAC9B,GAAI,KAAK,IAAI,oBAAsB,WACjC,OAAOA,EAAQ,EAEjB,IAAMgB,EAAQ,IAAM,CACd,KAAK,KAAK,oBAAsB,aAClC,KAAK,IAAI,oBAAoB,0BAA2BA,CAAK,EAC7DhB,EAAQ,EAEZ,EACA,KAAK,IAAI,iBAAiB,0BAA2BgB,CAAK,EAC1D,WAAW,IAAM,CACX,KAAK,KACP,KAAK,IAAI,oBAAoB,0BAA2BA,CAAK,EAE/DhB,EAAQ,CACV,EAAG,GAAG,CACR,CAAC,CACH,CAGA,OAAO,aAAaiB,EAAK,CACvB,IAAIC,EAAQD,EAAI,MAAM;AAAA,CAAM,EACtBE,EAAWD,EAAM,UAAWE,GAAMA,EAAE,WAAW,SAAS,CAAC,EAC/D,GAAID,IAAa,GAAI,CACnB,IAAME,EAAU,CAAC,EACXC,EAAa,IAAI,IAevB,GAdAJ,EAAM,QAASE,GAAM,CACnB,IAAMG,EAAIH,EAAE,MAAM,4BAA4B,EAC1CG,IAAKF,EAAQ,KAAKE,EAAE,CAAC,CAAC,EAAGD,EAAW,IAAIC,EAAE,CAAC,EAAG,CAAC,EACrD,CAAC,EACDL,EAAM,QAASE,GAAM,CACnB,GAAIA,EAAE,WAAW,SAAS,EAAG,CAC3B,IAAMI,EAAKJ,EAAE,MAAM,GAAG,EAAE,CAAC,EAAE,MAAM,GAAG,EAAE,CAAC,EACnCE,EAAW,IAAIE,CAAE,IACfJ,EAAE,SAAS,yBAAyB,EAAGE,EAAW,IAAIE,EAAI,GAAG,EACxDJ,EAAE,SAAS,yBAAyB,GAAGE,EAAW,IAAIE,EAAI,EAAE,EACjEJ,EAAE,SAAS,sBAAsB,GAAGE,EAAW,IAAIE,GAAKF,EAAW,IAAIE,CAAE,GAAK,GAAK,EAAE,EAE7F,CACF,CAAC,EACGH,EAAQ,OAAS,EAAG,CACtBA,EAAQ,KAAK,CAACI,EAAGC,IAAMJ,EAAW,IAAII,CAAC,EAAIJ,EAAW,IAAIG,CAAC,CAAC,EAC5D,IAAME,EAAQT,EAAMC,CAAQ,EAAE,MAAM,GAAG,EACjCS,EAAWD,EAAM,MAAM,CAAC,EAAE,OAAQH,GAAO,CAACH,EAAQ,SAASG,CAAE,CAAC,EACpEN,EAAMC,CAAQ,EAAI,CAAC,GAAGQ,EAAM,MAAM,EAAG,CAAC,EAAG,GAAGN,EAAS,GAAGO,CAAQ,EAAE,KAAK,GAAG,CAC5E,CACF,CACA,IAAMC,EAAWX,EAAM,UAAWE,GAAMA,EAAE,WAAW,SAAS,CAAC,EAC/D,GAAIS,IAAa,GAAI,CACnB,IAAMC,EAAU,CAAC,EAKjB,GAJAZ,EAAM,QAASE,GAAM,CACnB,IAAMG,EAAIH,EAAE,MAAM,4BAA4B,EAC1CG,GAAGO,EAAQ,KAAKP,EAAE,CAAC,CAAC,CAC1B,CAAC,EACGO,EAAQ,OAAS,EAAG,CACtB,IAAMH,EAAQT,EAAMW,CAAQ,EAAE,MAAM,GAAG,EACjCD,EAAWD,EAAM,MAAM,CAAC,EAAE,OAAQH,GAAO,CAACM,EAAQ,SAASN,CAAE,CAAC,EACpEN,EAAMW,CAAQ,EAAI,CAAC,GAAGF,EAAM,MAAM,EAAG,CAAC,EAAG,GAAGG,EAAS,GAAGF,CAAQ,EAAE,KAAK,GAAG,CAC5E,CACF,CACA,OAAOV,EAAM,KAAK;AAAA,CAAM,CAC1B,CAGA,oBAAoBX,EAAK,CACvB,OAAQA,EAAI,KAAM,CAChB,KAAKwB,EAAW,OACd,KAAK,cAAcxB,CAAG,EACtB,MACF,KAAKwB,EAAW,UACd,KAAK,iBAAiBxB,CAAG,EACzB,MACF,KAAKwB,EAAW,UACd,KAAK,gBAAgBxB,CAAG,EACxB,MACF,KAAKwB,EAAW,KACd,KAAK,MAAM,OAAQ,CACjB,OAAQxB,EAAI,QACZ,SAAUA,EAAI,SACd,KAAMA,EAAI,IACZ,CAAC,EACD,MACF,KAAKwB,EAAW,MACd,KAAK,MAAM,QAAS,IAAI,MAAMxB,EAAI,OAAO,CAAC,EAC1C,KACJ,CACF,CAGA,MAAM,cAAcA,EAAK,CACvB,GAAK,KAAK,IACV,GAAI,CACF,MAAM,KAAK,IAAI,qBACb,IAAI,sBAAsB,CAAE,KAAM,SAAU,IAAKA,EAAI,GAAI,CAAC,CAC5D,EACA,QAAWyB,KAAK,KAAK,gBACnB,MAAM,KAAK,IAAI,gBAAgBA,CAAC,EAElC,KAAK,gBAAkB,CAAC,CAC1B,OAASC,EAAG,CACV,KAAK,MAAM,QAASA,CAAC,CACvB,CACF,CAGA,MAAM,iBAAiB1B,EAAK,CAC1B,IAAM2B,EAAY,IAAI,gBAAgB,CACpC,UAAW3B,EAAI,UACf,OAAQA,EAAI,QACZ,cAAeA,EAAI,gBACrB,CAAC,EACD,GAAI,KAAK,KAAO,KAAK,IAAI,kBACvB,GAAI,CAAE,MAAM,KAAK,IAAI,gBAAgB2B,CAAS,CAAG,MAAY,CAAe,MAE5E,KAAK,gBAAgB,KAAKA,CAAS,CAEvC,CAGA,gBAAgB3B,EAAK,CAEnB,GAAIA,EAAI,YAAc,KAAK,OAAQ,OAEnC,IAAMK,EAAY,SAASL,EAAI,UAAU,QAAQ,UAAW,EAAE,EAAG,EAAE,EACnE,GAAI,CAACA,EAAI,UAAYA,EAAI,WAAa,GAAI,CAExC,IAAM4B,EAAW,KAAK,OAAO,IAAIvB,CAAS,EACtCuB,GACF,KAAK,OAAO,IAAIvB,EAAW,CAAE,UAAAA,EAAW,OAAQuB,EAAS,MAAO,CAAC,EAEnE,KAAK,MAAM,cAAe,CAAE,UAAAvB,EAAW,SAAUL,EAAI,SAAU,CAAC,EAChE,MACF,CACA,IAAM4B,EAAW,KAAK,OAAO,IAAIvB,CAAS,GAAK,CAAC,EAChD,KAAK,OAAO,IAAIA,EAAW,CACzB,GAAGuB,EACH,UAAAvB,EACA,SAAUL,EAAI,UACd,SAAUA,EAAI,SACd,SAAUA,EAAI,SAChB,CAAC,EACD,KAAK,MAAM,OAAQ,KAAK,OAAO,IAAIK,CAAS,CAAC,CAC/C,CAGA,sBAAuB,CACjB,KAAK,MACP,KAAK,IAAI,MAAM,EACf,KAAK,IAAM,MAEb,KAAK,gBAAkB,CAAC,CAC1B,CACF",
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", "res", "data", "isSecure", "wsProto", "FuzionXSignaling", "msg", "evt", "err", "r", "text", "SignalType", "c", "e", "candidate", "slotIndex", "existing", "videoTrackCount", "track", "stream", "slot", "state", "i", "offer", "finalSdp", "resolve", "check", "sdp", "lines", "videoIdx", "l", "h264Pts", "h264Scores", "m", "pt", "a", "b", "parts", "otherPts", "audioIdx", "opusPts", "FuzionXPublisher", "_FuzionXPublisher", "opts", "SessionMode", "DEFAULT_ICE_SERVERS", "MAX_SLOTS", "event", "handler", "args", "fn", "createRes", "data", "getRes", "isSecure", "wsProto", "baseUrl", "r", "text", "t", "track", "offer", "resolve", "localDesc", "url", "response", "answerSdp", "state", "FuzionXSignaling", "msg", "evt", "err", "i", "videoTrackCount", "slotIndex", "stream", "slot", "finalSdp", "check", "sdp", "lines", "videoIdx", "l", "h264Pts", "h264Scores", "m", "pt", "a", "b", "parts", "otherPts", "audioIdx", "opusPts", "SignalType", "c", "e", "candidate", "existing"]
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzionx/player",
3
- "version": "0.1.53",
3
+ "version": "0.1.55",
4
4
  "description": "FuzionX WebRTC Player SDK — Viewer & Publisher",
5
5
  "main": "dist/fuzionx-player.umd.js",
6
6
  "module": "dist/fuzionx-player.esm.js",
@@ -513,7 +513,6 @@ export class FuzionXPublisher {
513
513
  if (!msg.nickname || msg.nickname === '') {
514
514
  // stream 참조는 보존 (WebRTC 트랙은 PC 수명과 동일)
515
515
  const existing = this._slots.get(slotIndex);
516
- console.log('[FuzionX] slot_remove:', slotIndex, 'existing stream:', !!existing?.stream);
517
516
  if (existing) {
518
517
  this._slots.set(slotIndex, { slotIndex, stream: existing.stream });
519
518
  }
@@ -521,7 +520,6 @@ export class FuzionXPublisher {
521
520
  return;
522
521
  }
523
522
  const existing = this._slots.get(slotIndex) || {};
524
- console.log('[FuzionX] slot assign:', slotIndex, 'nickname:', msg.nickname, 'has stream:', !!existing.stream);
525
523
  this._slots.set(slotIndex, {
526
524
  ...existing,
527
525
  slotIndex,
@@ -252,7 +252,6 @@ export class FuzionXViewer {
252
252
  // nickname이 빈 문자열이면 슬롯 해제 (stream 참조는 보존)
253
253
  if (!msg.nickname || msg.nickname === '') {
254
254
  const existing = this._slots.get(slotIndex);
255
- console.log('[FuzionX-V] slot_remove:', slotIndex, 'existing stream:', !!existing?.stream);
256
255
  if (existing) {
257
256
  this._slots.set(slotIndex, { slotIndex, stream: existing.stream });
258
257
  }
@@ -261,7 +260,6 @@ export class FuzionXViewer {
261
260
  }
262
261
 
263
262
  const existing = this._slots.get(slotIndex) || {};
264
- console.log('[FuzionX-V] slot assign:', slotIndex, 'nickname:', msg.nickname, 'has stream:', !!existing.stream);
265
263
  this._slots.set(slotIndex, {
266
264
  ...existing,
267
265
  slotIndex,