# Canlı Ders Paneli: Video Görüşme + Eşzamanlı Harita Senkronizasyonu

**Araştırma raporu — uygulama değil, karar destek dokümanıdır.**
Tarih: 2026-07-17

## 0. Talebin Özeti

Astroloji öğretmeni ile öğrenci, mevcut panel sayfası (`resources/views/frontend/panel/panel.blade.php`) üzerinden:

1. Görüntülü görüşme yapabilmeli,
2. Öğretmenin panelde yaptığı her işlem (tarih/saat değişimi, ev sistemi, transit, harita değiştirme, sürükle-bırak) öğrencinin ekranında **eş zamanlı** gerçekleşmeli,
3. Panelin sol üstündeki küçük harita (sub-chart) slotu, canlı yayın sırasında **öğretmenin kamera görüntüsüyle** değişmeli,
4. Bunun için öğretmene özel, ek bir kontrol paneli olmalı,
5. Öğretmen ders sırasında ekranı "dondurup" üzerine **kalemle çizim/not** yapabilmeli — bu sırada kamera görüntüsü kesintisiz akmaya devam etmeli; çizim indirilebilir ve kaydedilebilir olmalı,
6. Dersler **kaydedilmeli**; öğretmen kendi panelinde "Ders Kayıtlarım" altında geçmiş derslerini bulabilmeli, öğrenci de kendi katıldığı derslerin tekrarını izleyebilmeli.

Bu rapor, beş alt problemi (video, senkronizasyon, UI entegrasyonu, eş zamanlı çizim, kayıt/arşiv) ayrı ayrı ele alıp performans/kullanılabilirlik/maliyet açısından en uygun yöntemi önerir.

---

## 1. Mevcut Mimarinin Analizi (bulgular)

Öneriler mevcut kod tabanına dayanıyor, bu yüzden önce ne bulduğumu özetliyorum:

| Bulgu | Kanıt | Etkisi |
|---|---|---|
| Backend: Laravel 12, PHP 8.2+ | `composer.json` | Laravel **Reverb** (native WebSocket) sorunsuz kullanılabilir, ek ücretli servise gerek yok |
| Frontend: Blade + vanilla JS (SPA yok), 5740 satırlık tekil `panel.js` | `public/frontend/js/panel/panel.js` | React/Vue gerektiren "component senkron" kütüphaneleri (Yjs/Liveblocks vb.) fazla mühendislik olur; DOM'a doğrudan event enjekte etmek daha uyumlu |
| **Hiç real-time altyapı yok** | `.env` → `BROADCAST_CONNECTION=log`, `routes/channels.php` yok | Sıfırdan kurulacak — bu bir avantaj: legacy bir sistemi migrate etmiyoruz |
| Harita render modeli **deterministik ve sunucu taraflı** | `panel.js` içinde `LOAD_CHART_LAZY_URL`, `CHART_TABS_URL`, `PANEL_LAYOUT_UPDATE_URL` fetch çağrıları; aynı `date/time/lat/lon/house_system/target_date` parametreleriyle SVG backend'de üretiliyor | **En kritik bulgu.** Ekranı piksel piksel senkronize etmeye gerek yok — sadece "hangi parametrelerle istek atıldı" bilgisini yayınlamak yeterli (bkz. Bölüm 3) |
| Küçük haritalar (sub-chart) `#leftList` / `#rightList` içinde `thumb-item` olarak, `draggable="true"` + Sortable.js ile ana alana (`#mainDropzone`) taşınabiliyor | `panel.blade.php:1629-1661`, `Sortable.min.js` | Video slotu bu sürükle-bırak sistemine karışmamalı (bkz. Bölüm 5) |
| Layout (hangi chart hangi slotta) sunucuya kaydediliyor | `persistPanelLayout()` → `PANEL_LAYOUT_UPDATE_URL` | Aynı mekanizma "canlı oturum state"i için model olarak genişletilebilir |

**Sonuç:** Bu iş göründüğünden daha az "video mühendisliği", daha çok "state senkronizasyon mühendisliği" gerektiriyor. Video tarafı aslında en kolay parça.

---

## 2. Video Görüşme Katmanı

### 2.1 Kritik karar: 1:1 görüşme — SFU'ya gerek yok

Görüntülü görüşme "teknik öğretmen ↔ tek öğrenci" şeklinde, yani **iki katılımcılı**. Bu, mimariyi büyük ölçüde ucuzlatıyor:

- **Grup görüşmede** (3+ kişi) her katılımcının stream'i N-1 kişiye gitmesi gerektiğinden bir **SFU** (Selective Forwarding Unit — LiveKit, mediasoup, Janus, Agora, Twilio) şart olur.
- **1:1 görüşmede** ise doğrudan **WebRTC P2P (peer-to-peer)** bağlantı yeterlidir: video/ses tamamen kullanıcıların cihazları arasında akar, sunucu sadece "signaling" (kim kiminle bağlanacak, SDP/ICE takası) için kullanılır — bu da zaten kuracağımız WebSocket katmanından (Bölüm 3) bedavaya gelir.

Bu yüzden pahalı bir "video SaaS" aboneliği almadan önce şunu netleştirmek gerekiyor:

### 2.2 Seçenek Karşılaştırması

| Yöntem | Kurulum zorluğu | Aylık maliyet (tahmini, ~50 eşzamanlı 1:1 seans) | Gecikme/kalite | Not |
|---|---|---|---|---|
| **Ham WebRTC P2P + kendi signaling (Reverb)** | Orta (signaling'i biz yazıyoruz, SDK yok) | **$0** yazılım + TURN maliyeti (~$5-20/ay, coturn self-host) | En düşük gecikme (doğrudan bağlantı), kalite ağ koşuluna bağlı | **Önerilen** — 1:1 için en ucuz ve en performanslı |
| **LiveKit (self-hosted OSS)** | Orta-yüksek (kendi sunucunuzda mediasoup tabanlı SFU) | Sunucu maliyeti (~$20-50/ay VPS) | İyi, simulcast/kayıt gibi ekstra özellikler gelir | Grup derse geçilecekse (1 öğretmen + çoklu öğrenci) mantıklı |
| **LiveKit Cloud / Daily.co (managed)** | Çok kolay (JS SDK, birkaç satır) | Dakika bazlı ücretlendirme (~$0.001-0.004/dk/katılımcı) → 50 seans × 60dk × 2 kişi ≈ $6-24/gün senaryoya göre | Çok iyi, kayıt/transkript hazır | Hızlı MVP için cazip ama ölçek büyüyünce maliyet lineer artar |
| **Agora / Twilio Video / Amazon Chime SDK** | Kolay | Genelde en pahalı, dakika+katılımcı bazlı | Kurumsal SLA, global düşük gecikme | Bizim ölçeğimizde gereksiz maliyet |
| **Basit ekran/canvas paylaşımı (screen-share tabanlı sözde "video")** | — | — | Kötü | Değerlendirilmedi; gerçek kamera görüşmesi isteniyor |

**Öneri:** MVP'yi **ham WebRTC P2P + kendi TURN sunucumuz (coturn)** ile kurmak. Sebep:
- Zaten Bölüm 3'te kurulacak WebSocket (Reverb) altyapısı signaling'i bedavaya taşıyor.
- 1:1 olduğu için SFU'nun sağladığı hiçbir avantaj (kayıt hariç) kullanılmıyor.
- Öğretmen sayısı büyüyüp "grup dersi" ihtiyacı doğarsa, o zaman LiveKit self-hosted'a geçiş (aynı signaling mantığıyla) nispeten kolay.

**Kayıt (recording) isteniyorsa:** Ham P2P'de kayıt, sunucu tarafında yapılmaz (çünkü stream sunucudan geçmiyor); istemci tarafında `MediaRecorder API` ile öğretmen tarafında lokal kayıt alınıp sunucuya upload edilebilir, ya da SFU'lu bir çözüme (LiveKit) geçilir. Bu netleşmesi gereken bir karar noktası — Bölüm 13'te "açık soru" olarak işaretledim.

### 2.3 TURN Sunucusu

WebRTC P2P bağlantılar bazı ağlarda (kurumsal/mobil NAT, simetrik NAT) doğrudan kurulamaz; bu durumda trafiği aktaran bir **TURN** sunucusuna ihtiyaç var (görüşmelerin ~%15-20'si için devreye girer, istatistiksel olarak).

| Seçenek | Maliyet | Not |
|---|---|---|
| **coturn (self-hosted)** | VPS maliyeti (~$5-10/ay) | Basit, kontrol bizde, öneri budur |
| Cloudflare Calls TURN | Kullanım bazlı, düşük | Kolay entegre, üçüncü parti bağımlılık |
| Twilio Network Traversal Service | Dakika bazlı | Managed, ekstra maliyet |
| Metered.ca TURN | Ücretsiz tier mevcut | Küçük ölçek için hızlı başlangıç |

---

## 3. Eşzamanlı Harita Senkronizasyonu (asıl mühendislik problemi burası)

### 3.1 Neden "ekranı piksel piksel yansıtma" YANLIŞ yaklaşım

İlk akla gelen yöntem "öğretmenin ekranını screen-share/canvas-stream ile öğrenciye göstermek" olabilir. Bunu **önermiyorum**:

- Bant genişliği maliyeti yüksek (video-benzeri sürekli stream).
- Öğrencinin panelinde metin/SVG **bulanıklaşır**, zoom'da kalite düşer.
- Öğrenci kendi panelinde bağımsız etkileşim (örn. kendi sekmesini açma) yapamaz — her şey "donmuş video" olur.
- SVG tabanlı haritalarımız zaten vektörel ve deterministik; bunu videoya çevirmek geriye doğru bir adım.

### 3.2 Önerilen yöntem: **Command Replication (komut yayını)**

Mevcut mimaride her harita değişikliği zaten şu döngüyle çalışıyor:

```
Öğretmenin tarayıcısı → fetch(LOAD_CHART_LAZY_URL / CHART_TABS_URL, {params}) → Backend SVG üretir → DOM güncellenir
```

Bunu senkronize etmenin en ucuz ve en performanslı yolu, bu adımı **iki kere** (öğretmen + öğrenci tarafında) tetiklemek yerine, **parametreleri** bir WebSocket kanalından yayınlamak:

```
Öğretmen bir input değiştirir (tarih, ev sistemi, transit, harita, sekme, dropzone swap)
   → panel.js mevcut fetch'i atar (öğretmen kendi ekranını günceller, HİÇBİR ŞEY DEĞİŞMEZ)
   → AYNI ANDA, aynı parametre objesi WebSocket üzerinden "session.{room_id}" kanalına yayınlanır
   → Öğrencinin tarayıcısı bu event'i dinler, AYNI fetch'i kendi tarafında tetikler
   → İki taraf da backend'den bağımsız ama DETERMİNİSTİK olarak aynı SVG/HTML'i alır
```

**Neden bu en doğru yöntem:**
- Yayınlanan veri sadece küçük bir JSON (`{action:"date_change", date:"1990-05-12", ...}`) — birkaç yüz byte, video/canvas stream'e kıyasla milyonda bir maliyet.
- Öğrencinin ekranı **native kalitede** (kendi tarayıcısının render ettiği gerçek SVG) kalır, bulanıklaşma yok.
- Zaten var olan `LOAD_CHART_LAZY_URL`, `CHART_TABS_URL`, `PANEL_LAYOUT_UPDATE_URL` fetch noktalarını "wrap" etmek yeterli — **panel.js'in %90'ını yeniden yazmaya gerek yok**, sadece bu fonksiyonların çağrıldığı ~10-15 noktaya ince bir "event yayınla" katmanı eklenir.
- Gecikme sadece WebSocket mesaj iletim süresi (~50-150ms) + öğrencinin kendi fetch'inin süresi kadardır; pratikte "eş zamanlı" hissi verir.

### 3.3 Alternatif (daha karmaşık, gerekirse): sonucu da gönderme

Eğer öğrencinin kendi fetch'ini atması (çift sunucu yükü + olası race condition, örn. öğrencinin tarayıcısı yanıtı öğretmenden farklı bir anda alması) istenmezse, öğretmenin fetch'i backend'den dönen HTML/SVG'yi **doğrudan** WebSocket event payload'ına da ekleyip öğrenciye "hazır sonucu" gönderebiliriz (sunucu tek sefer render eder, ikisine de dağıtır). Bu, backend yükünü yarıya indirir ama event payload'ı büyür (birkaç KB). **İkinci seans/ölçek büyüdükçe bu yaklaşıma geçmek mantıklı**; MVP için ilk yöntem (her iki taraf da kendi fetch'ini atar) yeterlidir.

### 3.4 WebSocket Altyapısı

| Seçenek | Maliyet | Not |
|---|---|---|
| **Laravel Reverb (self-hosted)** | $0 lisans, VPS'te ek process (~1 CPU core, düşük RAM) | **Önerilen.** Laravel 12 ile birinci sınıf entegrasyon, Sanctum session auth'u zaten kullandığımız için `routes/channels.php` yetkilendirmesi native çalışır |
| Pusher (managed) | Mesaj bazlı ücretlendirme, ücretsiz tier düşük seans sayısına yeter | Reverb'e göre gereksiz üçüncü parti bağımlılık, ama sunucu yönetmek istenmezse hızlı alternatif |
| Ably | Benzer, biraz daha pahalı | Aynı sebeple ikinci sırada |
| Soketi (Pusher-protokol uyumlu self-host) | $0 | Reverb'den önce popülerdi, Reverb artık resmi/daha entegre olduğu için önceliğimiz değil |

**Öneri: Laravel Reverb.** Ekstra SaaS maliyeti yok, mevcut auth sistemiyle native uyumlu, tek sunucuda düşük kaynak tüketir.

### 3.5 Kanal / Yetkilendirme Modeli

- Yeni bir `live_sessions` tablosu: `id, teacher_id, student_id, room_id (uuid), status (pending/active/ended), started_at, ended_at`.
- Private kanal: `private-session.{room_id}`.
- `routes/channels.php`: sadece `teacher_id` veya `student_id` eşleşen kullanıcı kanala girebilir (Sanctum session ile).
- **Yazma yetkisi asimetrik olmalı:** Öğretmen "host" rolüyle event yayınlayabilir; öğrenci sadece dinler (read-only). Bu, öğrencinin paneli kurcalayıp öğretmenin ekranını bozmasını da engeller — talebin doğasına da uygun ("öğretmenin yaptığı işlemler karşı tarafta gerçekleşmeli", tersi değil).
- Backend'de event yayınlarken **öğrenci soketinden gelen "yazma" event'lerini reddet** (yetki kontrolü sadece frontend'e bırakılmamalı — güvenlik notu, Bölüm 8).

### 3.6 Sürükle-bırak (drag&drop) senkronu

`Sortable.min.js` ile yapılan sürükleme, ham pointer/mouse event'lerini göndermek yerine **sonuç durumunu** yayınlamalı: "hangi chart key hangi slota gitti" (mevcut `getLayoutPayload()` fonksiyonunun ürettiği veriyle birebir aynı şekil). Öğrenci tarafında bu payload alınıp DOM aynı sıraya göre yeniden düzenlenir (Sortable'ın kendi API'siyla `sort()` çağrılır). Bu, ham mouse hareketlerini network üzerinden senkronize etmekten (jitter, düşük FPS, karmaşık) çok daha sağlam ve ucuzdur.

### 3.7 Sekme/tab ve diğer küçük etkileşimler

Aynı prensip: `CHART_TABS_URL` çağrısını tetikleyen her tıklama, "hangi tab, hangi chart key" bilgisini event olarak yayınlar; öğrenci tarafı aynı tab'ı açar.

---

## 4. Video Kamerasının Sub-Chart Slotuna Yerleştirilmesi (UI)

### 4.1 Neden mevcut sürükle-bırak sistemine "video"yu sokmamalıyız

`#leftList` içindeki `thumb-item` elemanları sürekli DOM'da yeniden düzenleniyor (`Sortable.js`, lazy-load ile `innerHTML` güncellemeleri — bkz. `LOAD_CHART_LAZY_URL` çağrıları). Bir `<video>` elementi, DOM'dan çıkarılıp yeniden eklendiğinde **MediaStream bağlantısı kopar/yeniden kurulur**, bu da görüntüde donma/flicker'a yol açar. Bu yüzden video elementini "chart gibi" sürüklenebilir bir öğe yapmak risklidir.

### 4.2 Önerilen yaklaşım

- Sol üstteki slot (`leftKeys` dizisinin ilk elemanı) için **ayrı, sabit bir DOM konteyneri** tanımlanmalı: örn. `#leftList` üstüne mutlak konumlu (`position:absolute` veya grid `z-index`) bir `#teacherVideoSlot` eklenir; canlı seans aktifken bu slot chart thumbnail'inin **üzerini kaplar** (`display:block`), seans yokken gizli kalır (`display:none`).
- Bu şekilde chart'ın kendi DOM node'u hiç dokunulmaz/silinmez — sadece üzerine bir video overlay biner. Sürükle-bırak/lazy-load mantığı etkilenmez, video da hiç yeniden mount edilmez (stream kopmaz).
- `<video>` elementine `srcObject` (WebRTC `MediaStream`) bir kez bağlanır, seans süresince aynı DOM node üzerinde kalır.
- Responsive: mobilde `leftList` zaten `d-none d-lg-block` (sadece büyük ekranda görünür) — küçük ekranlarda video için ayrı bir strateji gerekir (örn. ana chart'ın üstünde küçük "picture-in-picture" kutusu). Bu, mobil UX için ayrı bir tasarım kararı gerektirir (Bölüm 13'te açık soru).

### 4.3 Kalite/performans notu

Video elementi CSS ile küçük bir kutuya sıkıştırılsa da, WebRTC tarafında gönderilen çözünürlüğü de küçültmek (`getUserMedia` constraint'leri, örn. 320x240 @ 15fps) hem öğretmenin upload bant genişliğini hem CPU kullanımını azaltır — kutunun görsel boyutuna gerek olmayan yüksek çözünürlük göndermenin anlamı yok.

---

## 5. Öğretmen Paneli (Host Mode)

**Öneri: Ayrı bir sayfa/panel yazmak yerine, mevcut `panel.blade.php`'yi rol bayrağıyla genişletmek.**

Sebep: Öğretmenin gördüğü haritalar, sekmeler, formlar öğrenciyle birebir aynı olmalı (zaten "eş zamanlı" olması isteniyor) — ayrı bir panel yazmak, iki panelin sürüklenip senkronize kalmasını (iki farklı Blade/JS dosyasını paralel bakımda tutmayı) gerektirir, bu bakım yükünü ikiye katlar.

Bunun yerine:

- `panel.blade.php`'ye `is_host` / `is_live_session` gibi bir bayrak eklenir (query param veya session route'una göre: `/panel/live/{room_id}` teacher tarafında `?role=teacher`, öğrenci tarafında `?role=student`).
- **Host modunda ekstra bir kontrol çubuğu (toolbar)** render edilir: kamera aç/kapat, mikrofon aç/kapat, "seansı başlat/bitir", bağlantı durumu göstergesi.
- **Öğrenci modunda** form input'ları `disabled`/`readonly` yapılır (veya CSS ile `pointer-events:none`) — çünkü senkron tek yönlü (öğretmen → öğrenci); öğrencinin kendi tarafında farklı bir tarih girip iki ekranı "desync" etmesi istenmiyor.
- Bu yaklaşım, mevcut 5740 satırlık `panel.js`'i olduğu gibi kullanmaya devam etmemizi sağlar; sadece üstüne ince bir "canlı seans" katmanı (WebSocket bağlantısı + event yayın/dinleme + rol bazlı UI kısıtlama) eklenir.

---

## 5.5 Windows VDS Ortamına Özel Değerlendirme (Tamamen Ücretsiz Kurulum)

Repoda `public/web.config` ve `C:\Inetpub\vhosts\astrobilge.com` yolları görülüyor — bu sistem **Plesk for Windows + IIS** üzerinde çalışıyor, Linux değil. Bu, Bölüm 2-3'teki önerilerin bir kısmını değiştiriyor. Aşağıda "$0 maliyetle nasıl kurulur" sorusuna doğrudan cevap var.

### Ne değişiyor, ne değişmiyor

| Bileşen | Linux VDS varsayımıyla öneri | Windows VDS gerçeğiyle öneri | Neden değişti |
|---|---|---|---|
| Video (WebRTC P2P) | Ham WebRTC | **Aynı** — değişmiyor | WebRTC tamamen tarayıcı içinde çalışır, sunucu işletim sisteminden bağımsız |
| Signaling/senkron (Laravel Reverb) | coturn'le aynı Linux sunucuda | **Aynı sunucuda, ama Windows Service olarak** | Reverb, PHP + ReactPHP event loop ile çalışır — Windows'ta da native çalışır, sadece "arka planda sürekli açık kalması" farklı yönetilir |
| TURN sunucusu (coturn) | Self-host coturn | **coturn'den vazgeçilmeli** | coturn resmi olarak Linux'u hedefler; Windows'ta derleme/çalıştırma desteklenmiyor ve production'da güvenilir değil |

### Reverb'i Windows'ta ücretsiz çalıştırma

IIS, `php artisan reverb:start` gibi sürekli açık kalması gereken bir process'i doğal olarak yönetmiyor (IIS request-response modeliyle çalışır, "her zaman açık soket sunucusu" değildir). Çözüm — **hepsi ücretsiz araçlar:**

1. **NSSM** (Non-Sucking Service Manager, açık kaynak/ücretsiz) ile `php artisan reverb:start` komutunu bir **Windows Service** olarak kaydedin → sunucu yeniden başlasa bile otomatik ayağa kalkar, çökerse yeniden başlar.
2. IIS üzerinden dışarıya açmak için **Application Request Routing (ARR)** + **URL Rewrite** modülleri (ikisi de Microsoft'un ücretsiz IIS eklentileri) ile `wss://astrobilge.com/app` gibi bir path'i, Reverb'in dinlediği local porta (varsayılan 8080) reverse-proxy edin. Böylece ayrı bir subdomain/port/SSL sertifikası uğraşına girmeden mevcut domain sertifikanızı kullanabilirsiniz.
   - Alternatif (daha basit ama daha az temiz): Reverb'i doğrudan ayrı bir port üzerinden (örn. `wss://astrobilge.com:8080`) Windows Firewall'da açıp yayınlamak — IIS'i hiç karıştırmadan çalışır, ama ayrı bir SSL sertifikası (örn. ücretsiz win-acme/Let's Encrypt ile) gerektirir.
3. Kaynak tüketimi düşük (tek process, çoğunlukla boşta bekleyen WebSocket bağlantıları) — mevcut VDS'e ek bir sunucu kiralamaya gerek yok.

### STUN: Google'ın ücretsiz sunucusu kullanılmalı

WebRTC bağlantısının ilk adımı STUN'dır — "benim herkese açık IP/portum ne?" sorusuna cevap verir, TARAFLAR bunu öğrenip mümkünse **doğrudan** (P2P) bağlanır. STUN trafiği taşımaz, sadece adres keşfi yapar; bu yüzden maliyeti yoktur ve kendi sunucunuzda barındırmaya gerek yoktur.

**Öneri:** `stun:stun.l.google.com:19302` (ve Google'ın diğer `stun1-4.l.google.com:19302` adresleri, yedek olarak) — ücretsiz, kayıt gerektirmez, halihazırda milyonlarca üretim WebRTC uygulaması tarafından kullanılıyor. Bunu kullanmamak için bir sebep yok.

**Önemli sınır:** STUN, bağlantıların çoğunda (~%80-85) yeterlidir ama simetrik NAT arkasındaki kullanıcılarda (çoğu mobil operatör ağı, bazı kurumsal/okul ağları) **yetmez** — çünkü sadece adres söyler, trafiği taşımaz. O durumda devreye TURN girer (aşağıda). Yani STUN, TURN ihtiyacını ortadan kaldırmaz; onu **tamamlar** — ikisi birlikte konfigüre edilmeli (WebRTC `iceServers` listesinde STUN önce, TURN fallback olarak).

### TURN sunucusu için gerçekçi $0 seçenek

coturn'ü Windows'ta çalıştırmak yerine iki seçenek var:

| Seçenek | Maliyet | Not |
|---|---|---|
| **Ücretsiz genel TURN havuzu** (ör. Open Relay Project / metered.ca'nın ücretsiz TURN endpoint'i) | $0 | Kayıt gerektirmeden kullanılabiliyor, aylık ücretsiz kotası var; paylaşımlı olduğu için **SLA/garanti yok** — başlangıç ve orta ölçek için yeterli, kritik üretim yükü artarsa güncel kota/limitlerini sağlayıcının sitesinden doğrulamak gerekir |
| **Ücretsiz katmanlı bulut Linux VM'de coturn** (ör. Oracle Cloud "Always Free" VM) | $0 | Ayrı, küçük bir Linux sunucu gerektirir (Windows VDS'e ek olarak) — daha fazla operasyonel yük ama tamamen sizin kontrolünüzde, SLA endişesi yok |

**Öneri:** Başlangıçta ücretsiz genel TURN havuzuyla başlayıp, gerçek kullanım verisiyle ("bağlantıların TURN'e ne sıklıkla düştüğü") ihtiyaç doğarsa ikinci seçeneğe (ücretsiz Linux VM + coturn) geçmek. İkisi de $0 ama ilki kurulum/bakım gerektirmiyor.

### Sonuç: bu senaryoda maliyet gerçekten $0/ay olabilir mi?

**Evet** — aşağıdaki koşullarla:
- Reverb, mevcut Windows VDS'te ek bir "servis" olarak çalışır (NSSM), ayrı sunucu kiralanmaz.
- TURN için ücretsiz genel havuz kullanılır (SLA garantisi olmadan).
- Video kaydı yapılmaz (kayıt istenirse storage maliyeti eklenir, bu ayrı bir kalem).

Riski: paylaşımlı/ücretsiz TURN havuzunun kota/kararlılığı sizin kontrolünüzde değil — sistem büyüdükçe (özellikle mobil/kurumsal ağlardan bağlanan öğrenci oranı artarsa TURN'e düşen bağlantı oranı yükselir) bu noktayı izlemek ve gerekirse $5-10/ay'lık ücretsiz-katman-dışı bir Linux VM'e geçmeyi bir "yedek plan" olarak Bölüm 11'deki açık sorulara eklemek mantıklı.

---

## 6. Performans Değerlendirmesi

| Bileşen | Beklenen gecikme/yük | Not |
|---|---|---|
| Video (P2P WebRTC) | ~50-150ms (doğrudan bağlantı), TURN üzerinden ~100-250ms | 1:1 için SFU'dan daha düşük gecikme, çünkü ara sunucu yok |
| Chart senkron event'i (WebSocket) | ~30-100ms mesaj iletimi + öğrencinin kendi fetch süresi (backend render, mevcut sistemle aynı, tipik 100-400ms) | Toplamda "insan algısı" için yeterince hızlı (<1sn) |
| Sürükle-bırak senkronu | Event bazlı (pointer değil, sonuç), ~50-100ms | Ham mouse-move senkronize edilmediği için network jitter'dan etkilenmez |
| Sunucu yükü (Reverb) | Çok düşük — sadece küçük JSON event'ler yayınlıyor, video/ses sunucudan geçmiyor | 1:1 modelin en büyük avantajı |

---

## 7. Maliyet Değerlendirmesi (özet senaryo: 50 eşzamanlı 1:1 seans, ayda ~40 saat/öğretmen kullanım)

**Windows VDS'inizde (Bölüm 5.5'teki kurulumla) gerçekçi tablo:**

| Kalem | Önerilen yöntem | Tahmini aylık maliyet |
|---|---|---|
| Video (P2P) | Ham WebRTC, kendi signaling | $0 (yazılım) |
| TURN | Ücretsiz genel TURN havuzu (SLA garantisiz) | **$0** |
| WebSocket (senkron) | Laravel Reverb, mevcut Windows VDS'te Windows Service (NSSM) | **$0** (ek sunucu yok, sadece mevcut VDS'in kaynağını biraz kullanır) |
| Video kayıt (opsiyonel) | İstemci taraflı `MediaRecorder` + storage | Sadece storage maliyeti (mevcut disk/S3 fiyatına göre) |
| **Toplam (kayıt hariç)** | | **$0/ay** |

**Garantili/SLA'lı TURN isteseydiniz (yedek plan, Bölüm 5.5'te bahsedilen "büyürse" senaryosu):**

| Kalem | Alternatif | Tahmini aylık maliyet |
|---|---|---|
| TURN | Kendi Linux VPS'inizde coturn (Windows VDS'e ek olarak) | ~$5-10 |
| **Toplam** | | **~$5-10/ay** |

Karşılaştırma için: managed bir SFU (LiveKit Cloud/Daily/Agora) ile aynı kullanım hacmi, dakika bazlı ücretlendirmeyle kolayca **aylık $100-500+** seviyesine çıkabilir — çünkü 1:1 senaryoda SFU'nun sunduğu hiçbir teknik avantaj (grup yayını, simulcast) kullanılmıyor, sadece "kolay SDK" için ödeme yapılmış olur.

---

## 8. Güvenlik Notları

- Kanal yetkilendirmesi backend'de zorunlu (`routes/channels.php`) — sadece ilgili `teacher_id`/`student_id` kanala girebilmeli.
- **Yazma yetkisi backend'de de kontrol edilmeli**: öğrenci soketinden "chart değiştir" event'i gelirse sunucu bunu reddetmeli (sadece frontend'de disabled yapmak yeterli değil, birisi DevTools'tan event tetikleyebilir).
- TURN sunucusu için kısa ömürlü (`ephemeral`) kimlik bilgileri kullanılmalı (coturn'un `use-auth-secret` mekanizması), statik kullanıcı/şifre TURN credential'ı **yazma**.
- Video/mikrofon izinleri tarayıcı native `getUserMedia` izin akışıyla alınır; sunucu tarafında ekstra bir işlem gerekmez ama HTTPS zorunluluğu var (WebRTC sadece güvenli bağlamda çalışır) — üretim ortamında zaten SSL olmalı.
- Kayıt özelliği eklenirse, KVKK/GDPR açısından "kayıt yapılıyor" bilgilendirmesi ve onay akışı gerekir (hem öğretmen hem öğrenciden).

---

## 9. Önerilen Teknoloji Yığını (özet)

| Katman | Öneri | Alternatif (gerekirse) |
|---|---|---|
| Video | Ham WebRTC P2P | LiveKit self-hosted (grup derse geçilirse) |
| TURN | coturn (self-host) | Cloudflare Calls TURN |
| Signaling + state senkron | Laravel Reverb + Echo | Pusher/Ably (yönetim istenmezse) |
| Chart senkron yöntemi | Command replication (parametre event'i yayını) | Sonucu da gönderme (ölçek büyüyünce) |
| UI entegrasyonu | Sabit overlay slot (`#teacherVideoSlot`) | — |
| Öğretmen paneli | Aynı Blade + rol bayrağı (`is_host`) | Ayrı panel (bakım yükü fazla, önerilmez) |
| Çizim/whiteboard senkronu | Canvas overlay + stroke event yayını (aynı Reverb kanalı) | — |
| Ders kaydı | Kamera+ses `MediaRecorder` + ayrı **event-log** (chart/çizim event'lerinin timestamp'li kaydı) | Tam ekran video kaydı (depolama/kalite dezavantajlı, Bölüm 11.2) |

---

## 10. Eş Zamanlı Çizim (Whiteboard) Katmanı

### 10.1 Kapsam

Öğretmen, ders sırasında panelin ana alanını (harita/tablo ne gösteriyorsa) **dondurup** üzerine kalemle çizim/not yapabilmeli. Kamera slotu (Bölüm 4) bu dondurmadan etkilenmemeli — kamera akmaya devam etmeli. Çizim öğretmen tarafında **indirilebilir** (PNG) ve **kaydedilebilir** olmalı.

**Varsayım (netleştirilmesi gereken açık nokta, bkz. Bölüm 13):** Bu rapor boyunca kurulan "öğretmenin yaptığı her şey öğrencide eş zamanlı görünür" ilkesiyle tutarlı olması için çizimin de öğrenci ekranında canlı yayınlandığı varsayılıyor. Eğer çizim yalnızca öğretmenin kendi notu olacak, öğrenciye gösterilmeyecekse, aşağıdaki 10.4'teki senkron katmanına gerek kalmaz — sadece client-side bir canvas yeterli olur (mimariyi büyük ölçüde sadeleştirir).

### 10.2 "Dondurma" mekanizması

"Dondurmak", chart'ın DOM'dan kaldırılması değil — sadece etkileşimin geçici olarak kilitlenmesi:

- Chart'ı tetikleyen input/tıklama event listener'ları geçici devre dışı bırakılır (ör. bir `isFrozen` flag'i, mevcut event handler'ların başında kontrol edilir) veya panel üzerine `pointer-events:none` uygulanır.
- Chart'ın kendi DOM node'u (SVG) olduğu gibi kalır — **silinmez**, sadece üstüne şeffaf bir `<canvas>` biner (Bölüm 4.2'de video için kurulan "chart'a dokunmadan overlay ekleme" prensibiyle birebir aynı yaklaşım).
- Video slotu (`#teacherVideoSlot`) ayrı, sabit-konumlu bir DOM elemanı olduğu için bu dondurmadan hiç etkilenmez — kamera kesintisiz akmaya devam eder.

### 10.3 Araç çubuğu (toolbar)

| Araç | Not |
|---|---|
| Kalem | renk seçici + kalınlık |
| Silgi | stroke bazlı (tıklanan stroke'u siler) veya piksel silgi |
| Temizle (clear) | tüm canvas'ı boşaltır |
| Geri al / ileri al (undo/redo) | stroke listesi üzerinde işlem yapılır (bkz. 10.4) |
| Dondur / Çöz | dondurma moduna girip çıkma anahtarı |
| İndir (PNG) | client-side export |
| Kaydet | backend'e persist (bkz. 10.6) |

### 10.4 Senkronizasyon

Bölüm 3.2'de kurulan "command replication" prensibinin devamı: ham `pointermove` koordinatlarını sürekli göndermek yerine, çizilen **stroke**'u (nokta dizisi + renk + kalınlık) yayınlamak:

```
Öğretmen çizer → yerel canvas'a çizilir (kendi ekranı)
   → aynı stroke verisi WebSocket ile "session.{room_id}" kanalına yayınlanır (draw.stroke event'i)
   → öğrencinin tarayıcısı aynı stroke'u kendi canvas'ına çizer
```

- Performans: her `pointermove`'da ayrı event atmak yerine noktalar ~16-32ms aralıklarla toplu (batch) gönderilmeli — network trafiğini ve Reverb üzerindeki event sayısını azaltır.
- Diğer event tipleri: `draw.clear`, `draw.undo`, `draw.freeze` / `draw.unfreeze`.
- Yetki modeli Bölüm 3.5 ile aynı: sadece öğretmen (host) çizim event'i yayınlayabilir, öğrenci soketi bu event'i tetiklerse backend reddeder.

### 10.5 İndirme

Client-side: chart'ın anlık görüntüsü (SVG → canvas'a çizilir, örn. `canvg` veya native SVG'yi bir `<img>`'e çevirip `drawImage`) ile çizim katmanı bir offscreen canvas'ta birleştirilip `canvas.toDataURL()` üzerinden PNG olarak indirilir. Sunucuya gitmesine gerek yok.

### 10.6 Kaydetme (persist)

- Yeni tablo: `session_annotations` (`id, live_session_id, chart_snapshot_params (json), strokes (json), png_path (nullable), created_at`).
- Hem **vektör veri** (stroke listesi — küçük, sonradan yeniden yüksek çözünürlükte render edilebilir) hem opsiyonel **PNG export** saklanır (hızlı önizleme için).
- Bu kayıt, Bölüm 11'deki ders kaydı event log'una da doğal olarak eklenir — tekrar izlenirken çizimler de zamanlamasına göre yeniden "çizilir" (video olarak değil, gerçek stroke replay'i olarak).

---

## 11. Ders Kayıtları (Recording) ve Arşiv

### 11.1 Kapsam

Dersler kaydedilmeli; öğretmen kendi panelinde **"Ders Kayıtlarım"** altında geçmiş derslerini bulabilmeli, öğrenci de **kendi katıldığı** derslerin tekrarını izleyebilmeli.

### 11.2 Ne kaydedilecek, nasıl — iki yöntem karşılaştırması

Bölüm 2.2'de zaten not edildiği gibi, P2P mimaride video/ses sunucudan geçmiyor; kayıt için iki temel yaklaşım var:

| Yöntem | Ne yapılır | Depolama (1 saatlik ders) | Kalite | Karmaşıklık |
|---|---|---|---|---|
| A) Ham medya kaydı | Öğretmen tarafında `MediaRecorder API` ile kamera+ses **ve ekran** birlikte kaydedilip seans sonunda upload edilir | Yüksek (ekran dahilse birkaç GB/saat olabilir) | Chart kısmı gerçek ekran görüntüsü olduğu için bulanıklaşabilir — Bölüm 3.1'de reddedilen sorunun aynısı burada da geçerli | Düşük (tek `MediaRecorder`, ama büyük dosya yönetimi) |
| B) **Event-log tabanlı kayıt (Önerilen)** | Sadece kamera+ses `MediaRecorder` ile kaydedilir (ekran DEĞİL); chart/çizim event'leri (zaten Bölüm 3 ve 10'da WebSocket'e yayınlanıyor) zaman damgasıyla bir "event log" olarak saklanır | Çok düşük (kamera+ses dosyası + birkaç yüz KB event log) | En yüksek — tekrar izlerken chart yine vektörel/native render edilir, video değil | Orta — playback tarafında bir "event replayer" gerekir |

**Öneri: Yöntem B.** Rapor boyunca kurulan temel ilke ("pikseli değil, parametreyi/komutu senkronize et") kayıt için de geçerli — aynı avantaj (ucuz + bulanıklaşmayan kalite) burada da kazanılır.

### 11.3 Playback (tekrar izleme) mimarisi

- Kamera+ses `<video>` elementinde normal oynatılır.
- Event log, video'nun oynama zamanına senkronize "replay" edilir: video `timeupdate` event'i dinlenir, o ana kadar biriken chart/çizim event'leri sırayla mevcut render fonksiyonlarına (Bölüm 3'te zaten kurulan fetch/DOM güncelleme akışı) uygulanır.
- İleri/geri sarma (scrub): event log baştan hedef zamana kadar hızlıca "fast-forward" edilip DOM o ana getirilir.
- Yeni bir video player yazılmıyor — var olan panel render mantığı "canlıdan değil, kayıttan besleniyor" moduna alınıyor.

### 11.4 Veri modeli

- `live_sessions` tablosuna eklenecek alanlar: `recording_enabled` (bool), `camera_recording_path`, `event_log_path`.
- Event'ler seans boyunca (performans için) bellekte/Redis'te biriktirilip seans bitince tek bir JSON dosyası halinde diske/storage'a flush edilir — saniyede onlarca satır DB INSERT etmekten kaçınılır.

### 11.5 Depolama tahmini

| Kalem | Tahmini boyut/maliyet |
|---|---|
| Kamera+ses (düşük çözünürlük, ör. 320-480p @ ~500kbps) | ~200-250MB / saat |
| Event log (chart + çizim event'leri) | Birkaç yüz KB / saat — ihmal edilebilir |
| Depolama yeri | Windows VDS yerel disk (küçük ölçekte yeterli) veya ucuz object storage (Cloudflare R2, Backblaze B2 — GB başına çok düşük ücret) |

### 11.6 Erişim / Yetkilendirme

- **Öğretmen paneli:** "Ders Kayıtlarım" sayfası — `live_sessions` tablosundan `teacher_id = auth()->id()` filtresiyle, öğrenci/tarih bazlı listeleme ve arama.
- **Öğrenci paneli:** "Derslerim / Kayıtlarım" — sadece kendi `student_id`'siyle eşleşen seanslar listelenir.
- Video dosyasına **doğrudan public URL yok**; imzalı/süreli erişim (`Storage::temporaryUrl()` — R2/S3 kullanılıyorsa — veya local disk için yetki kontrollü bir controller action üzerinden stream) zorunlu.
- **Kayıt rızası (consent):** Bölüm 8'de açık soru olarak bırakılmıştı; artık özellik netleştiği için seans başlamadan önce her iki tarafa "Bu ders kaydediliyor" bildirimi + görünür bir "● Kayıtta" göstergesi gösterilmeli (KVKK/GDPR gerekliliği).

### 11.7 Saklama / silme politikası

Kaç ay/yıl saklanacağı ve öğretmen/öğrencinin kayıt silme talep edebilme hakkı netleştirilmeli — bu, Bölüm 13'teki açık sorulara eklendi.

---

## 12. Aşamalı Yol Haritası (öneri, uygulama planı değil)

1. **Faz 1 — Altyapı:** Laravel Reverb kurulumu, `live_sessions` tablosu, `routes/channels.php` yetkilendirmesi.
2. **Faz 2 — Chart senkron (video olmadan):** Mevcut fetch noktalarına event yayını eklenip, iki tarayıcı sekmesinde manuel test edilerek "eş zamanlı harita" doğrulanır. (Video olmadan test edilebilir bir aşama — riski erken düşürür.)
3. **Faz 3 — Video:** WebRTC P2P + TURN entegrasyonu, sabit video slotu.
4. **Faz 4 — Öğretmen paneli:** Rol bazlı UI kısıtlamaları, host toolbar, seans başlat/bitir akışı.
5. **Faz 5 — Whiteboard:** Dondurma mekanizması, canvas overlay, stroke senkronu, indirme/kaydetme.
6. **Faz 6 — Kayıt:** Event-log recorder, `MediaRecorder` entegrasyonu, upload, "Ders Kayıtlarım" (öğretmen) ve "Derslerim" (öğrenci) sayfaları, playback/event-replayer.
7. **Faz 7 — Cilalama:** Bağlantı kopma/yeniden bağlanma senaryoları, mobil uyum, saklama/silme politikası.

---

## 13. Açık Sorular / Riskler

| # | Soru | Neden önemli |
|---|---|---|
| 1 | ~~Görüşme kaydedilecek mi?~~ → **Evet, karara bağlandı** (Bölüm 11) | Event-log tabanlı kayıt yöntemi (B) seçildi |
| 2 | **Grup dersi** (1 öğretmen + N öğrenci) ileride planlanıyor mu? | Evet ise SFU'ya baştan yatırım yapmak mantıklı olabilir |
| 3 | Mobil (telefon/tablet) desteği zorunlu mu, `leftList` zaten mobilde gizli | Video slotu **ve** çizim katmanı mobil için ayrı tasarım gerektirir |
| 4 | Öğrencinin **kendi bağımsız etkileşimi** (örn. kendi notlarını açması) olacak mı yoksa tamamen kilitli mi izleyecek? | Yetkilendirme modelini (Bölüm 3.5) etkiler |
| 5 | Ağ koşulları kötü olan öğrenciler için "düşük bant genişliği modu" (sadece ses + chart senkron, video kapalı) gerekli mi? | Kullanılabilirlik için düşünülmeye değer, düşük ek maliyetle eklenir |
| 6 | Çizim, öğrenci ekranında da **canlı görünsün mü** yoksa sadece öğretmenin kendi notu mu olsun? (Bölüm 10.1'deki varsayım) | Senkron katmanının (10.4) gerekip gerekmediğini belirler |
| 7 | Kayıtlar **ne kadar süre** saklanacak, öğretmen/öğrenci silme talep edebilecek mi? (Bölüm 11.7) | KVKK/GDPR saklama politikası netleşmeli |
| 8 | Kayıt sırasında öğrenci **onay vermeyi reddederse** ders kayıtsız mı devam edecek, yoksa kayıt zorunlu mu? | Consent akışının (11.6) davranışını belirler |

---

## 14. Kapanış

Bu iş ilk bakışta bir "video entegrasyonu" gibi görünse de, gerçek karmaşıklık **video değil, harita etkileşiminin senkronizasyonu**. İyi haber: mevcut panel zaten deterministik, sunucu taraflı render mimarisine sahip olduğu için (ekranı görüntü olarak değil, "hangi parametreyle isteği attın" bilgisini yayınlayarak) bu senkronizasyon hem **çok ucuz** hem **çok yüksek kalitede** (piksel bulanıklaşması yok) çözülebilir. Video tarafı ise 1:1 olduğu için en basit ve en ucuz seçenek olan ham WebRTC P2P ile çözülebilir; SFU/managed-video-SaaS yatırımı ancak grup dersi ihtiyacı doğarsa gerekçelenir.

Aynı temel ilke — **"pikseli değil, komutu/parametreyi senkronize et"** — sonradan eklenen iki özellik için de geçerli oldu: çizim katmanı ham mouse hareketi yerine stroke event'i yayınlıyor (Bölüm 10), ders kaydı da tam ekran video yerine event-log + küçük kamera dosyası olarak saklanıyor (Bölüm 11). Bu tutarlılık tesadüf değil — panelin zaten vektörel/deterministik olması, projenin her yeni gereksinimi aynı ucuz ve yüksek kaliteli yaklaşımla çözmesini mümkün kılıyor.
