diff --git a/public/studio/Column.js b/public/studio/Column.js index a582925..e16bc3f 100644 --- a/public/studio/Column.js +++ b/public/studio/Column.js @@ -65,7 +65,7 @@ export default class Column { } const btn = document.createElement('button'); btn.className = `mwse-btn ${className ?? ''}`; - btn.textContent = label; + btn.innerHTML = label; // HTML destekler (ikonlu etiketler için) btn.addEventListener('click', onClick); this._actionsEl.appendChild(btn); } @@ -96,7 +96,12 @@ export default class Column { const icon = document.createElement('span'); icon.className = 'mwse-item__icon'; - icon.textContent = item.icon ?? '○'; + // Material Icons: icon adı string ise text, HTML içeriyorsa innerHTML + if (item.icon?.includes('<')) { + icon.innerHTML = item.icon; + } else { + icon.textContent = item.icon ?? 'circle'; + } row.appendChild(icon); const body = document.createElement('div'); @@ -120,7 +125,7 @@ export default class Column { if (item.hasChildren !== false) { const arrow = document.createElement('span'); arrow.className = 'mwse-item__arrow'; - arrow.textContent = '›'; + arrow.textContent = 'chevron_right'; row.appendChild(arrow); } diff --git a/public/studio/Studio.js b/public/studio/Studio.js index 40a5608..9af5359 100644 --- a/public/studio/Studio.js +++ b/public/studio/Studio.js @@ -1,19 +1,22 @@ // MWSE Studio — dahili yönetim arayüzü. -// Akış: bağlan → ID'yi kopyala → eşle → WebRTC ara / oda oluştur import ColumnView from '/studio/ColumnView.js'; import { MediaSources } from '/sdk/webrtc/index.js'; export default class Studio { constructor(mwse, container) { - this.mwse = mwse; - this._el = typeof container === 'string' + this.mwse = mwse; + this._el = typeof container === 'string' ? document.querySelector(container) : container; - this._view = new ColumnView(this._el); - this._devices = { cameras: [], microphones: [] }; - this._statusEl = null; + this._view = null; // mount() içinde oluşturulur + this._devices = { cameras: [], microphones: [] }; + this._statusEl = null; this._notifArea = null; - this._styleOK = false; - this._remoteMedia = []; + this._myIPEl = null; + this._myUUIDEl = null; + this._localGrid = null; // "Gönderiyorum" tile grid'i + this._remoteGrid = null; // "Geliyor" tile grid'i + this._streamsPanel = null; + this._styleOK = false; } async mount() { @@ -21,30 +24,36 @@ export default class Studio { this._injectStyle(); this._buildToolbar(); this._buildNotifArea(); + + // Ana alan: kolonlar (sol) + akış monitörü (sağ) + const mainArea = document.createElement('div'); + mainArea.className = 'mwse-studio__main'; + this._el.appendChild(mainArea); + + this._view = new ColumnView(mainArea); this._view.mount(); + this._streamsPanel = this._buildStreamsPanel(mainArea); + await this._loadDevices(); this._pushRootColumn(); - // Scope sonrası sanal IP al ve kendime ata. + // Sanal IP al this.mwse.scope(async () => { try { const ip = await this.mwse.virtualPressure.allocAPIPAddress(); - // PeerInfo.set → auth/info → mevcut tüm pairlere yayımlar. this.mwse.me.info.set('ip', ip); - this._updateMyIP(ip); + if (this._myIPEl) this._myIPEl.textContent = ip; + if (this._myUUIDEl) this._myUUIDEl.textContent = this.mwse.me.socketId.slice(-8); } catch (_) {} }); - // ── Gelen eşleme isteği → bildirim banner'ı ────────────────────── - this.mwse.me.on('request/pair', peer => { - this._showPairRequest(peer); - }); + // Gelen eşleme isteği → bildirim banner + this.mwse.me.on('request/pair', peer => this._showPairRequest(peer)); - // Eşleşme onaylandı (istek gönderen taraf) + // Eşleşme onaylandı this.mwse.me.on('accepted/pair', peer => { this._watchIncoming(peer); - // Yeni paire kendi IP'mizi paylaş. if (this.mwse.virtualPressure.APIPAddress) { this.mwse.me.info.set('ip', this.mwse.virtualPressure.APIPAddress); } @@ -52,91 +61,201 @@ export default class Studio { this._setStatus('online', `${this._peerLabel(peer)} eşleşmesi kuruldu`); }); - // Pair info değişince (karşı tarafın IP'si gelince) kolonları yenile. - this.mwse.me.on('accepted/pair', () => this._view.refresh()); - this.mwse.on('room', () => this._view.refresh()); this.mwse.me.on('end/pair', () => this._view.refresh()); + this.mwse.on('room', () => this._view.refresh()); return this; } - // Toolbar'daki IP/ID kartını güncelle. - _updateMyIP(ip) { - if (this._myIPEl) this._myIPEl.textContent = ip; - } - - // Bir peer için görünen ad: sanal IP varsa onu, yoksa kısa UUID. - _peerLabel(peer) { - return peer.info?.info?.ip || peer.socketId.slice(-8); - } - // ── Araç çubuğu ────────────────────────────────────────────────────────── _buildToolbar() { const bar = document.createElement('div'); bar.className = 'mwse-studio__toolbar'; - // Logo const logo = document.createElement('span'); logo.className = 'mwse-studio__title'; logo.innerHTML = 'MWSE Studio'; bar.appendChild(logo); - // Benim IP + ID kartı (tıkla → UUID kopyalanır) + // ID kartı: sadece IP + kısa UUID + kopyala const idCard = document.createElement('div'); idCard.className = 'mwse-id-card'; - idCard.title = 'Socket ID\'yi kopyala (paylaşmak için)'; + idCard.title = 'Socket ID kopyala (bağlantı paylaşımı için)'; - const idLabel = document.createElement('span'); - idLabel.className = 'mwse-id-card__label'; - idLabel.textContent = 'Kimliğim'; - - // Sanal IP (önce boş, allocAPIPAddress sonrası dolar) this._myIPEl = document.createElement('span'); this._myIPEl.className = 'mwse-id-card__ip'; - this._myIPEl.textContent = ''; + this._myIPEl.textContent = '●'; // IP gelmeden önce nokta - // Kısa UUID (ID paylaşımı için) - const idValue = document.createElement('span'); - idValue.className = 'mwse-id-card__value'; - idValue.textContent = '…'; + this._myUUIDEl = document.createElement('span'); + this._myUUIDEl.className = 'mwse-id-card__value'; + this._myUUIDEl.textContent = '…'; - const idCopy = document.createElement('span'); - idCopy.className = 'mwse-id-card__copy'; - idCopy.textContent = '⎘'; + const copyIcon = document.createElement('span'); + copyIcon.className = 'mwse-id-card__copy'; + copyIcon.textContent = '⎘'; - idCard.append(idLabel, this._myIPEl, idValue, idCopy); + idCard.append(this._myIPEl, this._myUUIDEl, copyIcon); idCard.addEventListener('click', () => { const id = this.mwse.me.socketId; - if (!id || id === '…') return; + if (!id) return; navigator.clipboard.writeText(id).then(() => { - idCopy.textContent = '✓'; + copyIcon.textContent = '✓'; idCard.classList.add('mwse-id-card--copied'); setTimeout(() => { idCard.classList.remove('mwse-id-card--copied'); - idCopy.textContent = '⎘'; + copyIcon.textContent = '⎘'; }, 2000); }); }); - - this.mwse.me.on('scope', () => { idValue.textContent = this.mwse.me.socketId.slice(-8); }); bar.appendChild(idCard); - // Durum this._statusEl = document.createElement('span'); this._statusEl.className = 'mwse-studio__status mwse-studio__status--online'; - this._statusEl.textContent = 'Bağlı'; + this._statusEl.innerHTML = 'wifi Bağlı'; bar.appendChild(this._statusEl); + // Canlı saat + const clock = document.createElement('span'); + clock.className = 'mwse-studio__clock'; + const tick = () => { + const now = new Date(); + clock.textContent = now.toTimeString().slice(0, 8); + }; + tick(); setInterval(tick, 1000); + bar.appendChild(clock); + this._el.insertBefore(bar, this._el.firstChild); } + // ── Akış monitör paneli ─────────────────────────────────────────────────── + + _buildStreamsPanel(parent) { + const panel = document.createElement('div'); + panel.className = 'mwse-streams-panel'; + panel.style.display = 'none'; // akış yokken gizli + + // Gönderiyorum bölümü + const outSec = document.createElement('div'); + outSec.className = 'mwse-streams-section'; + const outTitle = document.createElement('div'); + outTitle.className = 'mwse-streams-section__title'; + outTitle.textContent = '▲ Gönderiyorum'; + this._localGrid = document.createElement('div'); + this._localGrid.className = 'mwse-streams-grid'; + outSec.append(outTitle, this._localGrid); + + // Geliyor bölümü + const inSec = document.createElement('div'); + inSec.className = 'mwse-streams-section'; + const inTitle = document.createElement('div'); + inTitle.className = 'mwse-streams-section__title'; + inTitle.textContent = '▼ Geliyor'; + this._remoteGrid = document.createElement('div'); + this._remoteGrid.className = 'mwse-streams-grid'; + inSec.append(inTitle, this._remoteGrid); + + panel.append(outSec, inSec); + parent.appendChild(panel); + return panel; + } + + // Yerel (gönderilen) akış tile'ı ekle + _addLocalTile(label, stream, peerLabel) { + const hasVideo = stream.getVideoTracks().length > 0; + const tile = this._makeTile( + label, + `→ ${peerLabel}`, + stream, + hasVideo, + true, // muted (geri besleme yok) + () => tile.remove() && this._updatePanelVisibility() + ); + this._localGrid.appendChild(tile); + this._updatePanelVisibility(); + } + + // Uzak (gelen) track tile'ı ekle + _addRemoteTile(track, peerLabel) { + const isVideo = track.kind === 'video'; + const stream = new MediaStream([track]); + const tile = this._makeTile( + isVideo ? 'Video' : 'Ses', + `← ${peerLabel}`, + stream, + isVideo, + !isVideo, // ses tile'ı için muted değil (çalması lazım) + () => tile.remove() && this._updatePanelVisibility() + ); + // Ses için görünmez