Muzibu Player — Tam Veri Akış Haritası

Her Şarkı Çalındığında Ne Okunur, Ne Yazılır, Ne Silinir — Uçtan Uca v2

READ INSERT UPDATE DELETE — Bu renkler tüm rapor boyunca kullanılır

Basit Anlatım — Herkes İçin

Bir şarkıya "Çal" dediğinizde arka planda 5 katmanlı bir kontrol mekanizması çalışıyor:

  1. Kimlik Kontrolü: Giriş yapmış mısın? Çerezlerinden (cookie) bakıyor.
  2. Üyelik Kontrolü: Premium üye misin? Abonelik bitiş tarihin geçmemiş mi?
  3. Şarkı Hazırlığı: Şifreli stream URL'si üretiliyor, şifreleme anahtarı ayrıca gönderiliyor.
  4. Veri Toplama: Kim, ne zaman, hangi cihazdan, kaç saniye dinledi — hepsi kaydediliyor.
  5. Bellek Yönetimi: Eski şarkının izleri siliniyor, yeni şarkının verisi yükleniyor.

Aşağıdaki diyagram tüm bu sürecin haritası. Mavi = okunan, Yeşil = eklenen, Turuncu = güncellenen, Kırmızı = silinen veri.

Tam Mimari Diyagram — Şarkı Çalma Döngüsü

KULLANICI "ÇAL" BUTONUNA BASIYOR
          │
          ▼
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│                              TARAYICI (Client-Side)                                      │
│                                                                                         │
│  📖 OKUNAN VERİLER:                                                                    │
│  ┌─────────────────────────────────────────────────────────────────────────────────┐    │
│  │ localStorage:                                                                   │    │
│  │  • volume → Ses seviyesi (0-100)                                              │    │
│  │  • muzibu_player_state → Son durum (şarkı, sıra, pozisyon)                   │    │
│  │  • muzibu_play_context → Nereden çalındı (albüm/playlist/arama)              │    │
│  │  • muzibu_device_profile_id → Cihaz profil ID                               │    │
│  │  • muzibu_device_fingerprint → Cihaz parmak izi                              │    │
│  │  • muzibu_spot_counter → Reklam sayacı (kaç şarkı dinlendi)                  │    │
│  │                                                                                 │    │
│  │ Alpine.js Store:                                                                │    │
│  │  • store('favorites').favorites → ["song-123","album-45"]                    │    │
│  │  • store('player').currentSong → Mevcut şarkı bilgisi                       │    │
│  │  • store('player').queue → Çalma sırası                                     │    │
│  │                                                                                 │    │
│  │ Memory:                                                                         │    │
│  │  • streamUrlCache (Map) → Önceden alınmış stream URL'leri (max 30, 5dk TTL) │    │
│  │  • HlsPool._active → Aktif HLS instance'ları (max 3)                       │    │
│  └─────────────────────────────────────────────────────────────────────────────────┘    │
│          │                                                                              │
│          ▼                                                                              │
│  🗑️ SİLİNEN VERİLER (Eski Şarkı Temizliği):                                         │
│  ┌─────────────────────────────────────────────────────────────────────────────────┐    │
│  │ • Eski HLS instance → HlsPool.release() (listener kaldır, detach, destroy)    │    │
│  │ • Eski Blob URL → URL.revokeObjectURL() (bellek serbest)                      │    │
│  │ • Eski Audio element src → removeAttribute('src') + load()                    │    │
│  │ • Eski Howler instance → howl.off() + howl.stop() + howl.unload()              │    │
│  │ • Eski Event Listener'lar → onended, onerror, onpause, onstalled kaldırılır   │    │
│  │ • Preload verisi → _preloadedNext temizlenir                                   │    │
│  │ • Progress interval → clearInterval(progressInterval)                          │    │
│  │ • Buffer health interval → clearInterval(_bufferCheckInterval)                 │    │
│  │ • Play count timer → clearTimeout(playCountTimerId)                            │    │
│  └─────────────────────────────────────────────────────────────────────────────────┘    │
│          │                                                                              │
│          ▼                                                                              │
│  ┌─────────────────────┐    HTTP/HTTPS     ┌──────────────────────────────────────┐    │
│  │ POST /api/stream    │ ──────────────→  │ SUNUCU (Server-Side)              │    │
│  │ + cookie + CSRF      │                   │                                      │    │
│  └──────────┬──────────┘    ◄──────────────  │  1. auth('web') → Session cookie oku  │    │
│             │               JSON Response   │  2. isPremium() → DB query:          │    │
│             │                               │     users.subscription_expires_at    │    │
│             │                               │     > NOW() mu?                      │    │
│             │                               │  3. getSong() → Redis cache VEYA DB  │    │
│             │                               │  4. SignedUrl üret (HMAC-SHA256)    │    │
│             │                               │  5. Response: şifreli URL + meta     │    │
│             │                               └──────────────────────────────────────┘    │
│             ▼                                                                           │
│  📝 EKLENEN VERİLER (Yeni Şarkı):                                                   │
│  ┌─────────────────────────────────────────────────────────────────────────────────┐    │
│  │ Memory (Alpine Store):                                                          │    │
│  │  • currentSong = { id, title, cover, duration, album, artist, color_hash }   │    │
│  │  • queue[index] güncellenir                                                  │    │
│  │  • streamUrlCache.set(songId) → signed URL cache'e eklenir                   │    │
│  │  • currentPlayId = API'den dönen play_id                                     │    │
│  │  • playbackStartTime = Date.now()                                            │    │
│  │                                                                                 │    │
│  │ DOM:                                                                            │    │
│  │  • <audio> element → yeni src + event listener'lar eklenir                │    │
│  │  • MediaSession → title, artist, artwork (kilit ekranı)                      │    │
│  │  • document.title = "Şarkı Adı - Sanatçı | Muzibu"                          │    │
│  │  • Blob URL → stream URL için yeni blob oluşturulur                          │    │
│  │  • HLS instance → HlsPool.acquire() ile yeni instance                       │    │
│  │                                                                                 │    │
│  │ localStorage:                                                                   │    │
│  │  • muzibu_player_state → güncel şarkı + sıra + pozisyon kaydedilir          │    │
│  │  • muzibu_play_context → { songId, albumId, context, timestamp }             │    │
│  │  • muzibu_spot_counter → spot sayacı +1 (30s dinlenirse)                     │    │
│  │                                                                                 │    │
│  │ Timer'lar:                                                                      │    │
│  │  • progressInterval → 250ms aralıkla ilerleme çubuğu güncelleme             │    │
│  │  • _bufferCheckInterval → 500ms aralıkla buffer sağlık kontrolü             │    │
│  │  • playCountTimerId → 30s sonra play_count artır (setTimeout)                │    │
│  │  • queueMonitorInterval → 10s'de bir sıra doluluk kontrolü                   │    │
│  └─────────────────────────────────────────────────────────────────────────────────┘    │
│          │                                                                              │
│          │  Şarkı çalmaya başladı, arka planda devam eden işlemler:                        │
│          │                                                                              │
│          ├──→ POST /api/track-start ───→ SUNUCU: INSERT muzibu_song_plays              │
│          │    (hemen)                    { song_id, user_id, device_profile_id,          │
│          │                                ip_address, source_type, created_at }          │
│          │                              ◄── { play_id: 12345 }                          │
│          │                                                                              │
│          ├──→ POST /api/track-hit ────→ SUNUCU: UPDATE songs SET play_count+1            │
│          │    (30 saniye sonra)                                                          │
│          │                                                                              │
│          ├──→ GET /api/songs/next/stream → Sonraki şarkının URL'si (preload)            │
│          │    (şarkının %80'inde)                                                       │
│          │                                                                              │
│          ├──→ HLS segment indirme ───→ Her 3-5 saniyede ~100KB segment                  │
│          │    (sürekli)                                                                  │
│          │                                                                              │
│          └──→ POST /api/track-end ────→ SUNUCU: UPDATE muzibu_song_plays              │
│               (şarkı bitince veya        SET ended_at, listened_duration,                │
│                geçince veya              was_skipped, stop_reason                        │
│                tab kapatılınca)                                                          │
│                                                                                         │
└─────────────────────────────────────────────────────────────────────────────────────────┘
          │
          ▼
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│                              SUNUCU (Server-Side) — Veritabanı İşlemleri                  │
│                                                                                         │
│  📖 OKUNAN TABLOLAR:                                                                   │
│  ┌────────────────────────────────────────────────────────────────────────────┐         │
│  │ users                    → id, subscription_expires_at (isPremium check)  │         │
│  │ muzibu_songs             → song_id, hls_path, duration, encryption_key    │         │
│  │ muzibu_albums            → album bilgisi (eager load)                     │         │
│  │ muzibu_artists           → sanatçı bilgisi (eager load)                   │         │
│  │ muzibu_song_plays        → duplicate check (son 5 saniye)                 │         │
│  │ Redis: song:{id}         → Song metadata cache (24 saat TTL)              │         │
│  │ Redis: popular_songs     → Popüler şarkı cache (30 dakika TTL)            │         │
│  │ Redis: enc_key_{id}      → Şifreleme anahtarı cache (24 saat TTL)         │         │
│  │ Dosya: enc.bin           → AES-128 şifreleme anahtarı (16 byte)           │         │
│  │ Dosya: master.m3u8       → HLS playlist (modifiye edilerek serve edilir)  │         │
│  │ Dosya: segment-*.ts      → Şifreli ses parçaları                          │         │
│  └────────────────────────────────────────────────────────────────────────────┘         │
│                                                                                         │
│  📝 EKLENEN VERİLER (INSERT):                                                         │
│  ┌────────────────────────────────────────────────────────────────────────────┐         │
│  │ muzibu_song_plays  ← YENİ KAYIT (her şarkı başlangıcında)                │         │
│  │   { song_id, user_id, device_profile_id, ip_address,                      │         │
│  │     source_type, source_id, created_at }                                  │         │
│  └────────────────────────────────────────────────────────────────────────────┘         │
│                                                                                         │
│  🔄 GÜNCELLENEN VERİLER (UPDATE):                                                     │
│  ┌────────────────────────────────────────────────────────────────────────────┐         │
│  │ muzibu_songs.play_count  ← +1 INCREMENT (30 saniye sonra)                │         │
│  │ muzibu_song_plays        ← ended_at, listened_duration, was_skipped,     │         │
│  │                             stop_reason (şarkı sonunda)                   │         │
│  └────────────────────────────────────────────────────────────────────────────┘         │
│                                                                                         │
│  🗑️ SİLİNEN VERİLER:                                                                   │
│  ┌────────────────────────────────────────────────────────────────────────────┐         │
│  │ Sunucu tarafında hiçbir veri silinmiyor.                                   │         │
│  │ Tüm dinleme kayıtları saklanır (analitik için).                           │         │
│  └────────────────────────────────────────────────────────────────────────────┘         │
└─────────────────────────────────────────────────────────────────────────────────────────┘
10+
Okunan Veri Kaynağı
DB + Redis + File + localStorage
7+
Eklenen Veri
DB insert + localStorage + DOM
4+
Güncellenen Veri
play_count + state + store
9+
Silinen/Temizlenen
HLS + Blob + Audio + Timer

Üyelik & Yetki Kontrol Zinciri

Bir şarkı çalma isteğinde sunucunun yaptığı 4 aşamalı kontrol

Aşama 1: Giriş Yapılmış mı?

READ

Kontrol: auth('web')->user() ?? auth('sanctum')->user()

Okunan: Session cookie (laravel_session) veya Bearer token

Başarısız: 401 Unauthorized

→ Tarayıcıda: Tüm localStorage temizlenir, /login'e yönlendirilir

Aşama 2: Premium Üye mi?

READ

Kontrol: $user->isPremium() → iki katmanlı:

// 1. Model değeri (hızlı, cache'den)
if ($this->subscription_expires_at && $this->subscription_expires_at->isFuture()) {
    return true;
}

// 2. Fresh DB sorgusu (model stale olabilir)
$freshExpiry = DB::table('users')
    ->where('id', $this->id)
    ->value('subscription_expires_at');

return $freshExpiry && Carbon::parse($freshExpiry)->isFuture();

Tek Kriter: subscription_expires_at > NOW()

NULL= Free kullanıcı → 402
2025-12-31 (geçmiş)= Süresi dolmuş → 402
2026-06-15 (gelecek)= Aktif premium → devam

402 Payment Required (Free / Süresi Dolmuş)

{
  "status": "subscription_required",
  "redirect": "/subscription/plans",
  "message": "Müzik dinlemek için premium üyelik gerekli",
  "song": { "id": 123, "title": "...", "cover_url": "..." },
  "is_premium": false,
  "subscription_ends_at": null
}

Stream URL dönmez. Şarkı çalınamaz. Kullanıcı abonelik sayfasına yönlendirilir.

Aşama 3: Şarkı HLS'e Dönüştürülmüş mü?

READ

Kontrol: $song->hls_path dolu mu?

hls_path dolu → HLS stream URL üret, status: "ready"
hls_path boş → MP3 fallback URL üret + ConvertToHLSJob dispatch, status: "converting"

Aşama 4: Signed URL Üretimi

READ

İmza: HMAC-SHA256(path + userId + expires, APP_KEY)

TTL: Şarkı süresi + 5 dk buffer (min 30dk, max 60dk)

Şifreleme: Stream URL XOR + Base64 ile şifrelenip tarayıcıya gönderilir

Oturum Sonlandırma Akışı (401 / Force Logout)

Tetikleyiciler

  • Başka cihazdan giriş
  • Session süresi doldu
  • CSRF token uyuşmazlığı
  • Abonelik iptal

Silinen Veriler (Tarayıcı)

  • muzibu_player_state
  • muzibu_queue
  • muzibu_favorites
  • muzibu_play_context
  • muzibu_volume
  • sessionStorage.clear()
  • ServiceWorker cache

Yönlendirme

Hard redirect (SPA bypass):

/login?session_terminated=1

Modal: "Başka bir cihazdan giriş yapıldı"

Şarkı Yaşam Döngüsü — Zaman Çizelgesi

Bir şarkının başlangıcından sonuna kadar yapılan tüm veri işlemleri

T0: Çal Butonuna Basıldı (0ms)

6 READ 9 DELETE

Okunan:

  • localStorage: volume, player_state, play_context
  • Alpine Store: currentSong, queue, queueIndex
  • streamUrlCache: varsa cached URL kullanılır

Silinen (eski şarkı):

  • HLS instance → HlsPool.release()
  • Blob URL → revokeObjectURL()
  • Audio src → removeAttribute('src')
  • Howler → .off() + .stop() + .unload()
  • Timer'lar → clearInterval/clearTimeout (3 adet)

T1: API Çağrısı — /api/stream (100-400ms)

5 READ

Sunucuda okunan:

1.Session cookie→ auth('web') kullanıcı kimliği
2.users.subscription_expires_at→ Premium kontrolü (FRESH DB query)
3.Redis: song:{id}→ Şarkı metadata cache (miss ise DB)
4.muzibu_songs→ hls_path, duration, encryption_key
5.config('app.key')→ HMAC imza için

T2: Yeni Şarkı Yükleme (150-600ms)

12 WRITE

Tarayıcıya Eklenen:

  • + HLS instance (HlsPool.acquire)
  • + Blob URL (stream URL gizleme)
  • + Audio element src + listeners
  • + MediaSession metadata (kilit ekranı)
  • + document.title güncelleme
  • + progressInterval (250ms timer)
  • + bufferCheckInterval (500ms timer)
  • + playCountTimer (30s setTimeout)

Store/Storage Güncelleme:

  • + Alpine: currentSong = yeni şarkı
  • + Alpine: isPlaying = true
  • + streamUrlCache.set(songId, url)
  • + localStorage: muzibu_player_state
  • + localStorage: muzibu_play_context

T3: Ses Çıktı + Arka Plan Başladı (300ms-1.2s)

1 INSERT

Sunucuya yazılan:

POST /api/track-start → INSERT INTO muzibu_song_plays:
  song_id          = 123
  user_id          = 456
  device_profile_id = 42 (veya NULL)
  ip_address       = "185.x.x.x"
  source_type      = "album" (playlist, genre, search, radio...)
  source_id        = 789
  created_at       = NOW()
← Response: { play_id: 12345 }

Duplicate koruması: Aynı şarkı + aynı kullanıcı 5 saniye içinde tekrar INSERT yapılmaz.

T4: 30 Saniye Sonra — Play Count (30s)

2 UPDATE
POST /api/track-hit → UPDATE muzibu_songs
  SET play_count = play_count + 1
  WHERE song_id = 123

+ Tarayıcıda:
  localStorage: muzibu_spot_counter = songsPlayed + 1
  (Reklam sayacı artırılır)

T5: Şarkının %80'i — Preload Sonraki (değişken)

READ WRITE

Sonraki şarkı için yeni /api/stream çağrısı, yeni HLS instance, yeni blob URL oluşturulur.

Ayrıca reklam (spot) sistemi: shouldPreloadSpot() kontrolü → spot audio preload başlar.

T6: Şarkı Bitti / Geçildi / Tab Kapatıldı

1 UPDATE
POST /api/track-end → UPDATE muzibu_song_plays
  SET ended_at          = NOW()
      listened_duration = 187 (saniye)
      was_skipped       = false
      stop_reason       = "ended"
  WHERE id = 12345
    AND user_id = 456
    AND ended_at IS NULL

stop_reason değerleri:
  "ended"        → Şarkı doğal bitti
  "next"         → Kullanıcı ileri bastı
  "prev"         → Kullanıcı geri bastı
  "pause_timeout"→ Uzun süre durduruldu
  "close"        → Tab/pencere kapatıldı (sendBeacon)

localStorage — Tam Harita

Key Değer Ne Zaman Yazılır Ne Zaman Silinir Yazan Modül
volume 0-100 (integer) Ses değiştiğinde Logout player-core.js
muzibu_player_state JSON: {currentSongId, isPlaying, currentTime, volume, repeatMode, shuffle, queue...} Her şarkı değişiminde + periyodik Logout player-core.js
muzibu_queue JSON: minimal queue (mevcut +-2 + sonraki 20) Queue değiştiğinde Logout player-core.js
muzibu_play_context JSON: {songId, albumId, context, position, timestamp} Her şarkı başlangıcında Logout player-core.js
muzibu_favorites JSON: ["song-123", "album-45", ...] Sayfa yüklendiğinde (API'den) Logout favorites.js
muzibu_spot_counter Integer: Dinlenen şarkı sayısı Her şarkı 30s dinlendiğinde +1 Reklam çalınca sıfırlanır spot-player.js
muzibu_spot_counter_settings JSON: spot ayar versiyonu Spot settings yüklendiğinde Versiyon değişince üzerine yazılır spot-player.js
muzibu_device_profile_id Integer: Backend device profile ID İlk ziyarette (bir kez) Cihaz değişirse üzerine yazılır device-profiler.js
muzibu_device_fingerprint String: base64 hash (32 char) İlk ziyarette (bir kez) Cihaz değişirse üzerine yazılır device-profiler.js
remembered_email String: e-posta (Remember Me) Login form "Beni Hatırla" Manuel silme auth.js

Bellekte (RAM) Tutulan Veriler

Alpine.js Store Verileri

currentSongŞarkı objesi (~2KB)
queue[]Şarkı listesi (~50KB)
queueIndexMevcut pozisyon (int)
isPlayingboolean
volume0-100 (int)
durationSaniye (float)
currentTimeSaniye (float)
repeatMode"none" / "all" / "one"
shuffleboolean

Gizli State (İzleme)

currentPlayIdAPI play_id (track-end için)
playbackStartTimeDate.now() (süre hesabı)
totalListenedMsToplam dinleme ms
hitTracked30s play count gönderildi mi?
streamUrlCacheMap (max 30 entry, 5dk)
HlsPool._activeHLS instance'ları (max 3)
_preloadedNextSonraki şarkı preload datası
_currentBlobUrlAktif blob URL referansı

Veritabanı İşlemleri — Her Şarkı İçin

GET /api/stream — Okunan Veriler

0-2 Query
userssubscription_expires_at (FRESH DB query)
Redis: song:{id}Song metadata (24h cache, miss ise DB'den)
muzibu_songshls_path, duration, encryption_key, encryption_iv
muzibu_albumsalbum adı, cover (eager load)
muzibu_artistssanatçı adı (eager load)

POST /api/track-start — Yazılan Veriler

3 Query (1 INSERT)
READSong exists check (1 query)
READDuplicate check: son 5s aynı şarkı (1 query)
INSERTmuzibu_song_plays: song_id, user_id, device_profile_id, ip, source_type, created_at

POST /api/track-hit — Güncellenen (30s sonra)

2 Query (1 UPDATE)
READPlay ownership check: id + user_id doğrulama
UPDATEmuzibu_songs: play_count = play_count + 1

POST /api/track-end — Güncellenen (şarkı sonunda)

2 Query (1 UPDATE)
READPlay check: id + user_id + ended_at IS NULL
UPDATEended_at, listened_duration, was_skipped, stop_reason

HLS Dosya Servisi — Okunan

0 Query (dosya)
Dosyamaster.m3u8 — Playlist (modifiye: HIGH quality kaldırılıyor, key URL ekleniyor)
Dosyasegment-*.ts — Şifreli ses parçaları (direkt serve, immutable cache)
Redis/Dosyaenc.bin — AES-128 şifreleme anahtarı (16 byte, Redis 24h cache)

Toplam DB İşlemi (Tek Şarkı)

5-7
SELECT
1
INSERT
2
UPDATE
0
DELETE

Sunucu tarafında hiçbir veri silinmiyor — tüm dinleme kayıtları saklanır.

Her Şarkı Geçişinde Silinen / Temizlenen Veriler

Eski şarkıdan yeni şarkıya geçerken tarayıcıda yapılan temizlik

Bellekten Silinen (RAM)

HLS instance

HlsPool.release() → stopLoad() → detachMedia() → removeAllListeners() → destroy()

~20 MB serbest

Blob URL

URL.revokeObjectURL(_currentBlobUrl)

~1 KB + referans serbest

Howler instance

howl.off('end/load/loaderror') → howl.stop() → howl.unload()

~5-10 MB serbest (MP3 ise)

Preload verisi

_preloadedNext = null (preloaded HLS + audio temizlenir)

~20 MB serbest

DOM'dan Silinen

Audio element src

audio.removeAttribute('src') → audio.load()

Element DOM'da kalır ama kaynak serbest

Event Listener'lar

onended, onerror, onpause, onstalled, onwaiting, onseeked → null

6+ listener kaldırılır

Timer'lar

clearInterval(progressInterval) — 250ms timer
clearInterval(_bufferCheckInterval) — 500ms timer
clearTimeout(playCountTimerId) — 30s timer

3 timer temizlenir

Kritik Temizlik Sırası

1. HlsPool.release(hls) ← ÖNCE bu (listener'ları kaldır)

2. revokeBlobUrl(blobUrl) ← URL serbest bırak

3. safeAudioCleanup(audio) ← SONRA bu

Ters sıra = audio.load() HLS listener'larını tetikler → internalException!

Çıkış Yapılınca Silinen Her Şey

localStorage

  • muzibu_player_state
  • muzibu_queue
  • muzibu_favorites
  • muzibu_play_context
  • muzibu_volume

Kalan: device_profile_id, device_fingerprint, remembered_email, spot_counter

sessionStorage

  • sessionStorage.clear()
  • Tüm session verileri temizlenir

Diğer

  • ServiceWorker cache
  • Alpine store → reset
  • HLS instance'ları → destroy
  • Blob URL'ler → revoke

Tam Döngü — 1 Şarkı Hayatı

══════════════════════════════════════════════════════════════════════
  1 ŞARKININ TAM HAYAT DÖNGÜSÜ — NE OKUNUR, NE YAZILIR, NE SİLİNİR
══════════════════════════════════════════════════════════════════════

[T0 — 0ms] KULLANICI "ÇAL" DIYOR
│
├─ READ  localStorage.volume                    → Ses seviyesi al
├─ READ  localStorage.muzibu_device_profile_id   → Cihaz profili
├─ READ  Alpine.store('player').currentSong      → Eski şarkı bilgisi
├─ READ  streamUrlCache.get(songId)              → Varsa cached URL kullan
│
├─ DEL   Eski HLS instance                       → HlsPool.release()    [-20MB RAM]
├─ DEL   Eski Blob URL                           → revokeObjectURL()    [-1KB]
├─ DEL   Eski Audio src + listeners              → null + load()        [-buffer]
├─ DEL   Eski Howler (MP3 ise)                   → .off().stop().unload()[-10MB]
├─ DEL   Eski preload                            → _preloadedNext=null  [-20MB]
├─ DEL   progressInterval                        → clearInterval()
├─ DEL   _bufferCheckInterval                    → clearInterval()
├─ DEL   playCountTimerId                        → clearTimeout()
│
├──→ POST /api/stream/{songId} ──→ SUNUCU
│    │
│    ├─ READ  Session cookie                      → Kullanıcı kimliği
│    ├─ READ  users.subscription_expires_at       → Premium mi? (FRESH DB)
│    ├─ READ  Redis: song:{id}                    → Cache (24h TTL)
│    ├─ READ  muzibu_songs + album + artist       → Metadata (cache miss)
│    ├─ READ  config('app.key')                   → HMAC imzalama
│    │
│    └─ Response: { şifreli stream_url, şifreli fallback_url, song metadata }
│
[T1 — 100-400ms] API YANIT GELDİ
│
├─ ADD  Alpine.currentSong = yeni şarkı        → Store güncelleme
├─ ADD  streamUrlCache.set(songId, url)         → URL cache'e ekle
├─ ADD  HlsPool.acquire()                      → Yeni HLS instance      [+20MB]
├─ ADD  Blob URL oluştur                       → Stream URL'yi gizle
├─ ADD  Audio element: src + 6 event listener  → DOM güncellemesi
├─ ADD  MediaSession: title, artist, artwork   → Kilit ekranı güncelle
├─ ADD  document.title = "Şarkı - Sanatçı"    → Sekme başlığı
├─ UPD  localStorage.muzibu_player_state       → Güncel durum kaydet
├─ UPD  localStorage.muzibu_play_context       → Yeni context kaydet
│
│  ┌─ HLS.js: master.m3u8 indir → enc.bin key al → segment-0.ts indir
│  └─ AES-128 decrypt → Audio decode → Ses çıkışı
│
[T2 — 300ms-1.2s] SES ÇIKIYOR
│
├─ ADD  progressInterval  (250ms)               → İlerleme çubuğu timer
├─ ADD  bufferCheckInterval (500ms)             → Buffer sağlık timer
├─ ADD  playCountTimerId  (30s setTimeout)      → Play count timer
│
├──→ POST /api/track-start ──→ SUNUCU
│    ├─ READ  Duplicate check (son 5s)            → Aynı şarkı var mı?
│    ├─ INSERT muzibu_song_plays                 → Yeni dinleme kaydı
│    │   { song_id, user_id, device_profile_id, ip, source_type, created_at }
│    └─ Response: { play_id: 12345 }
│
├─ ADD  currentPlayId = 12345                  → RAM'de tut
├─ ADD  playbackStartTime = Date.now()         → Süre hesabı için
│
[T3 — 30 saniye sonra] PLAY COUNT
│
├──→ POST /api/track-hit ──→ SUNUCU
│    ├─ READ  Play ownership doğrulama            → play_id + user_id
│    └─ UPDATE muzibu_songs.play_count + 1      → Dinlenme sayısı arttır
│
├─ UPD  hitTracked = true                      → Tekrar gönderme
├─ UPD  localStorage.muzibu_spot_counter + 1    → Reklam sayacı arttır
│
[T4 — Şarkının %80'i] PRELOAD SONRAKI
│
├──→ GET /api/stream/{nextSongId} ──→ SUNUCU (aynı akış)
├─ ADD  _preloadedNext = { hls, audio, ready } → Sonraki hazırla    [+20MB]
├─ ADD  Yeni Blob URL (preload için)
│
│  (Spot sistemi: songsPlayed >= songsBetween ise reklam preload et)
├─ ADD  preloadedSpot + preloadedAudio (varsa)                    [+5MB][T5 — Şarkı Bitti] TRACK END + TEMİZLİK
│
├──→ POST /api/track-end ──→ SUNUCU
│    ├─ READ  Play check (ended_at IS NULL)
│    └─ UPDATE muzibu_song_plays
│        SET ended_at = NOW()
│            listened_duration = 210 (saniye)
│            was_skipped = false
│            stop_reason = "ended" | "next" | "prev" | "close"
│
├─ UPD  localStorage.muzibu_player_state       → Son durum kaydet
│
└─ → Sonraki şarkı başlar (T0'a dön) veya durur

══════════════════════════════════════════════════════════════════════
  ÖZET:  ~10 READ  |  ~12 WRITE  |  ~5 UPDATE  |  ~9 DELETE/CLEANUP
  DB:     5-7 SELECT | 1 INSERT   | 2 UPDATE    | 0 DELETE
══════════════════════════════════════════════════════════════════════