Studio: gelen akış fix + ses kısma kontrolü

_watchIncoming: _ensureRTC(peer) çağrısı eklendi
  - Önceden RTC başlatılmıyordu → gelen :rtcpack: sinyalleri receive() içinde
    _neg=null nedeniyle sessizce düşüyordu → track olayı hiç ateşlenmiyordu
  - Şimdi kabul/eşleşme anında RTC başlatılıyor (polite = küçük socketId)
  - Gelen offer'ı Negotiator işleyebiliyor → answer gönderiliyor → track geliyor

_addRemoteTile yeniden yazıldı:
  - streams parametresi eklendi (RTCPeerConnection'ın streams dizisi kullanılır)
  - Video: <video autoplay> (muted=false, gerçek ses çalar)
  - Ses: gizli <audio autoplay> + görsel tile (graphic_eq ikonu)
  - Ses kıs/aç butonu (volume_up ↔ volume_off, Material Icons)
  - Kapatınca audio.srcObject=null + remove() (bellek sızıntısı önlendi)
  - Track ended → tile otomatik kalkar

CSS:
  .mwse-stream-tile__mute (yeşil=açık, kırmızı=sessiz)
  .mwse-stream-tile__close Material Icons 'close' metni

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
abdussamedulutas 2026-06-17 14:03:42 +03:00
parent c1d1ddf383
commit f5565f5df0
2 changed files with 94 additions and 21 deletions

View File

@ -175,30 +175,83 @@ export default class Studio {
this._updatePanelVisibility(); this._updatePanelVisibility();
} }
// Uzak (gelen) track tile'ı ekle // Uzak (gelen) track tile'ı → ses kontrollü
_addRemoteTile(track, peerLabel) { _addRemoteTile(track, streams, peerLabel) {
const isVideo = track.kind === 'video'; const isVideo = track.kind === 'video';
const stream = new MediaStream([track]); const stream = streams?.[0] ?? new MediaStream([track]);
const tile = this._makeTile(
isVideo ? 'Video' : 'Ses', const tile = document.createElement('div');
`${peerLabel}`, tile.className = `mwse-stream-tile${isVideo ? '' : ' mwse-stream-tile--audio'}`;
stream,
isVideo, // Media element
!isVideo, // ses tile'ı için muted değil (çalması lazım) let mediaEl;
() => tile.remove() && this._updatePanelVisibility() if (isVideo) {
); mediaEl = document.createElement('video');
// Ses için görünmez <audio> da ekle mediaEl.autoplay = true;
if (!isVideo) { mediaEl.playsInline = true;
const audio = new Audio(); mediaEl.muted = false;
audio.autoplay = true; mediaEl.srcObject = stream;
audio.srcObject = stream; mediaEl.className = 'mwse-stream-tile__video';
document.body.appendChild(audio); tile.appendChild(mediaEl);
} else {
// Ses: görünür tile + gizli <audio> elemanı
mediaEl = document.createElement('audio');
mediaEl.autoplay = true;
mediaEl.muted = false;
mediaEl.srcObject = stream;
document.body.appendChild(mediaEl);
const icon = document.createElement('div');
icon.className = 'mwse-stream-tile__audio-icon';
icon.textContent = 'graphic_eq';
tile.appendChild(icon);
} }
// Alt bilgi çubuğu: etiket + ses kıs/aç + kapat
const info = document.createElement('div');
info.className = 'mwse-stream-tile__info';
const lbl = document.createElement('span');
lbl.className = 'mwse-stream-tile__label';
lbl.textContent = isVideo ? 'Video' : 'Ses';
const peerEl = document.createElement('span');
peerEl.className = 'mwse-stream-tile__peer';
peerEl.textContent = `${peerLabel}`;
// Ses kısma butonu
const muteBtn = document.createElement('span');
muteBtn.className = 'mwse-stream-tile__mute';
muteBtn.textContent = 'volume_up';
muteBtn.title = 'Sesi kıs / aç';
let muted = false;
muteBtn.addEventListener('click', e => {
e.stopPropagation();
muted = !muted;
mediaEl.muted = muted;
muteBtn.textContent = muted ? 'volume_off' : 'volume_up';
muteBtn.classList.toggle('mwse-stream-tile__mute--off', muted);
});
const closeBtn = document.createElement('span');
closeBtn.className = 'mwse-stream-tile__close';
closeBtn.textContent = 'close';
closeBtn.title = 'Tile kapat';
closeBtn.addEventListener('click', e => {
e.stopPropagation();
if (!isVideo) { mediaEl.srcObject = null; mediaEl.remove(); }
tile.remove();
this._updatePanelVisibility();
});
info.append(lbl, peerEl, muteBtn, closeBtn);
tile.appendChild(info);
this._remoteGrid.appendChild(tile); this._remoteGrid.appendChild(tile);
this._updatePanelVisibility(); this._updatePanelVisibility();
// Track bitince tile'ı kaldır // Track kapandığında tile'ı kaldır
track.addEventListener('ended', () => { track.addEventListener('ended', () => {
if (!isVideo) { mediaEl.srcObject = null; mediaEl.remove(); }
tile.remove(); tile.remove();
this._updatePanelVisibility(); this._updatePanelVisibility();
}); });
@ -604,8 +657,12 @@ export default class Studio {
// Gelen track'leri izle → panel'e ekle // Gelen track'leri izle → panel'e ekle
_watchIncoming(peer) { _watchIncoming(peer) {
peer.rtc.on('track', track => { // RTC başlatılmazsa gelen :rtcpack: sinyalleri receive() içinde _neg=null
this._addRemoteTile(track, this._peerLabel(peer)); // nedeniyle sessizce düşer, track olayı hiç ateşlenmez.
this._ensureRTC(peer);
peer.rtc.on('track', (track, streams) => {
this._addRemoteTile(track, streams, this._peerLabel(peer));
this._setStatus('online', `${this._peerLabel(peer)} ${track.kind}`); this._setStatus('online', `${this._peerLabel(peer)} ${track.kind}`);
}); });
} }

View File

@ -169,9 +169,25 @@
.mwse-stream-tile__label { color: #ccc; font-weight: 600; flex: 1; } .mwse-stream-tile__label { color: #ccc; font-weight: 600; flex: 1; }
.mwse-stream-tile__peer { color: #555; } .mwse-stream-tile__peer { color: #555; }
.mwse-stream-tile__mute {
font-family: 'Material Icons Round';
font-size: 15px;
cursor: pointer;
color: #4caf50;
padding: 1px 3px;
border-radius: 2px;
flex-shrink: 0;
line-height: 1;
}
.mwse-stream-tile__mute:hover { background: #2a2a2a; }
.mwse-stream-tile__mute--off { color: #f44336; }
.mwse-stream-tile__close { .mwse-stream-tile__close {
cursor: pointer; color: #444; font-size: 12px; font-family: 'Material Icons Round';
font-size: 14px;
cursor: pointer; color: #444;
padding: 1px 3px; border-radius: 2px; padding: 1px 3px; border-radius: 2px;
flex-shrink: 0; line-height: 1;
} }
.mwse-stream-tile__close:hover { background: #333; color: #ccc; } .mwse-stream-tile__close:hover { background: #333; color: #ccc; }