Fix Brifing Dokümanı · v2

Sorun #2 ve #5 — Tam Teknik Anlatım

Dosya: public/themes/muzibu/js/player/core/player-core.js

Bu doküman fix yapacak developer/AI için hazırlanmıştır. Kod değiştirilmemiştir.

🔴 KRİTİK · SORUN #2

Paylaşımlı _lastTimeupdateProcess

Sorun ne?

Dosyada 6 farklı ontimeupdate handler var. Her biri saniyede yüzlerce kez ateşlenen bu event'i 250ms'ye throttle etmek için aynı tekniği kullanıyor — "son ne zaman çalıştım?" diye bir değişkene bakıyor, 250ms geçmediyse erken çıkıyor.

Problem şu: 6 handler'ın hepsi self._lastTimeupdateProcess adlı tek bir değişkeni paylaşıyor. Handler A çalıştı ve timestamp güncelledi. Handler B 30ms sonra çalışmak istiyor ama bakıyor: "250ms geçmemiş" → geri dönüyor. B'nin çalışmaması gereken bir durum değildi — aynı değişkeni paylaştığı için bloke oldu.

Çözüm basit: Her handler kendi değişkenini kullanacak. 6 satır değişiklik (her handler'da 1 rename).

Mevcut 6 handler — hepsi aynı değişken

Her handler'da bu üç satır var, sadece bağlam (hangi audio) farklı:

Handler 1 — Satır ~2601 nextAudio.ontimeupdate (crossfade path)
// 🎵 CROSSFADE TRIGGER: timeupdate event for crossfaded HLS (throttled 250ms)
nextAudio.ontimeupdate = function() {
    // 🚀 THROTTLE: 250ms
    const _now = performance.now();
    if (_now - (self._lastTimeupdateProcess || 0) < 250) return;  // ← SORUN
    self._lastTimeupdateProcess = _now;                              // ← SORUN

    if (!self.duration || self.duration <= 0) return;
    if (self.isCrossfading) return;
    // ... crossfade tetikleme mantığı ...
Handler 2 — Satır ~2942 spotAudio.ontimeupdate (spot reklam)
// 🎙️ PROGRESS: Spot için timeupdate listener (progress bar güncelleme + preload)
spotAudio.ontimeupdate = function() {
    // 🚀 THROTTLE: 250ms
    const _now = performance.now();
    if (_now - (self._lastTimeupdateProcess || 0) < 250) return;  // ← SORUN
    self._lastTimeupdateProcess = _now;                              // ← SORUN

    if (self._isPlayingSpot && spotAudio.duration > 0) {
        self.currentTime = spotAudio.currentTime;
        // ... spot progress güncelleme ...
Handler 3 — Satır ~4025 preloadedAudio.ontimeupdate (Safari native preload)
// 🎵 TIMEUPDATE: Preload + Spot Preload + Crossfade trigger (throttled 250ms)
preloadedAudio.ontimeupdate = function() {
    // 🚀 THROTTLE: 250ms
    const _now = performance.now();
    if (_now - (self._lastTimeupdateProcess || 0) < 250) return;  // ← SORUN
    self._lastTimeupdateProcess = _now;                              // ← SORUN
    // (Safari native preload path)
Handler 4 — Satır ~4208 preloadedAudio.ontimeupdate (HLS.js preload path)
// 🎵 TIMEUPDATE: Preload + Spot Preload + Crossfade trigger (HLS.js preloaded, throttled 250ms)
preloadedAudio.ontimeupdate = function() {
    // 🚀 THROTTLE: 250ms
    const _now = performance.now();
    if (_now - (self._lastTimeupdateProcess || 0) < 250) return;  // ← SORUN
    self._lastTimeupdateProcess = _now;                              // ← SORUN
    // (HLS.js preload path)
Handler 5 — Satır ~5232 audio.ontimeupdate (ANA PLAYER — en kritik)
// 🎵 CROSSFADE TRIGGER: timeupdate event (throttled 250ms)
// Bu event page hidden olsa bile düzgün çalışır
audio.ontimeupdate = function() {
    // 🚀 THROTTLE: 250ms — saniyede 4-250 ateşlenme → max 4
    const _now = performance.now();
    if (_now - (self._lastTimeupdateProcess || 0) < 250) return;  // ← SORUN
    self._lastTimeupdateProcess = _now;                              // ← SORUN

    // ... duration kontrol, preload tetikleme, crossfade tetikleme ...
Handler 6 — Satır ~5443 audio.ontimeupdate (Safari path)
// 🎵 CROSSFADE TRIGGER: timeupdate event for Safari (throttled 250ms)
audio.ontimeupdate = function() {
    // 🚀 THROTTLE: 250ms
    const _now = performance.now();
    if (_now - (self._lastTimeupdateProcess || 0) < 250) return;  // ← SORUN
    self._lastTimeupdateProcess = _now;                              // ← SORUN
    // ... Safari path ...

Fix — Her handler kendi değişkenini kullanacak

Yapılacak işlem: Her handler'daki iki satırda sadece değişken adını değiştir. Başka hiçbir şey değişmiyor — throttle süresi, mantık, konum aynı kalıyor.

Handler 1 · Satır ~2605–2606 → Değişken adı: _lastTimeupdateCrossfadeNext

    const _now = performance.now();
    if (_now - (self._lastTimeupdateCrossfadeNext || 0) < 250) return;
    self._lastTimeupdateCrossfadeNext = _now;

Handler 2 · Satır ~2945–2946 → Değişken adı: _lastTimeupdateSpot

    const _now = performance.now();
    if (_now - (self._lastTimeupdateSpot || 0) < 250) return;
    self._lastTimeupdateSpot = _now;

Handler 3 · Satır ~4028–4029 → Değişken adı: _lastTimeupdatePreloadSafari

    const _now = performance.now();
    if (_now - (self._lastTimeupdatePreloadSafari || 0) < 250) return;
    self._lastTimeupdatePreloadSafari = _now;

Handler 4 · Satır ~4211–4212 → Değişken adı: _lastTimeupdatePreloadHls

    const _now = performance.now();
    if (_now - (self._lastTimeupdatePreloadHls || 0) < 250) return;
    self._lastTimeupdatePreloadHls = _now;

Handler 5 · Satır ~5235–5236 → Değişken adı: _lastTimeupdateMain

    const _now = performance.now();
    if (_now - (self._lastTimeupdateMain || 0) < 250) return;
    self._lastTimeupdateMain = _now;

Handler 6 · Satır ~5446–5447 → Değişken adı: _lastTimeupdateSafari

    const _now = performance.now();
    if (_now - (self._lastTimeupdateSafari || 0) < 250) return;
    self._lastTimeupdateSafari = _now;

Önemli not: Bu yeni değişkenler Alpine store'a önceden tanımlanmasına gerek yok. JavaScript'te undefined|| 0 ile sıfıra düşüyor, ilk çalışmada zaten doğru davranıyor. Sadece handler içindeki isimleri değiştirmek yeterli.

Neden önemli? — Senaryo

// 16 saatlik çalmada, reklam çalıyorken ne oluyor?

T=0ms : spotAudio.ontimeupdate ateşlendi

→ _lastTimeupdateProcess = 1000ms

T=30ms : audio.ontimeupdate ateşlendi (ANA PLAYER)

→ (1000+30) - 1000 = 30ms < 250 → RETURN! ← Bloke oldu

→ Crossfade tetikleme mantığı çalışmadı

→ Preload tetikleme mantığı çalışmadı

// Fix sonrası:

T=0ms : spotAudio.ontimeupdate → _lastTimeupdateSpot = 1000ms

T=30ms : audio.ontimeupdate → _lastTimeupdateMain bakıyor

→ _lastTimeupdateMain = 0 (hiç çalışmamış)

→ 30ms > 0ms → ÇALIŞTI ✅

🔴 KRİTİK · SORUN #5

Online Recovery Eksik — HLS.js Kör Kalıyor

Sorun ne?

İnternet kesilip geri geldiğinde sistem sadece şarkı kuyruğunu dolduruyor. Ama şu an çalmakta olan şarkıyı kurtarmıyor.

HLS.js, internet kesildiğinde segmentleri yüklemeye çalışır, defalarca başarısız olur, sonunda fatal error durumuna geçip tamamen durur. Müzik sessiz kalır. Online gelince sistem "queue doldu, hazırım" diyor ama HLS.js hâlâ dondurulmuş, audio element çalmıyor. Sessizlik devam eder.

Bunun yanı sıra, offline süresince şarkının imzalı URL'si (token) süresi dolmuş olabilir. HLS.js uyansa bile 403/401 alır.

Mevcut kod — Konum: satır ~7598–7608

Fonksiyon: enableBackgroundPlayback() içindeki window.addEventListener('online', ...) bloğu

window.addEventListener('online', () => {
    this._networkOffline = false;
    console.log('✅ Bağlantı geldi — queue yenileniyor');

    // 1 saniye bekle (bağlantı stabilize olsun) sonra queue doldur
    setTimeout(() => this.checkAndRefillQueue(), 1000);

    // ← Bitti. Sadece bu kadar.
    // ← HLS.js'e hiçbir şey söylenmiyor
    // ← audio.play() çağrılmıyor
    // ← Token yenileme yok
});

Tam hata zinciri — adım adım

1

Müzik çalıyor. HLS.js segment yüklüyor. Her şey normal.

2

Wi-Fi kesildi. window 'offline' ateşlendi → _networkOffline = true. HLS.js segment isteği başarısız, retry başladı.

3

HLS.js 5 kez retry yaptı (errorRetry: maxNumRetry: 5). Hepsi başarısız. HLS.js fatal error durumuna geçti ve stopLoad() yaptı. Müzik sustu.

4

Şarkının imzalı token'ı 5–15 dakika içinde süre dolmasıyla geçersiz olabilir (sunucu konfigürasyonuna bağlı).

5

Wi-Fi geri geldi. window 'online' ateşlendi → _networkOffline = false → 1sn sonra checkAndRefillQueue().

6

checkAndRefillQueue() API'den yeni şarkılar çekti, queue doldu. Ama HLS.js hâlâ durdurulmuş. audio.play() çağrılmadı. Müzik çalmıyor.

7

Kafe görevlisi fark etti (ya da etmedi). El müdahalesi olmadan müzik kendi kendine başlamaz.

Fix — enableBackgroundPlayback() içindeki online handler

Mevcut window.addEventListener('online', ...) bloğunu tamamen şununla değiştir:

window.addEventListener('online', () => {
    this._networkOffline = false;
    console.log('✅ Bağlantı geldi — tam recovery başlatılıyor');

    setTimeout(async () => {
        // ── ADIM 1: HLS.js'i uyandır ──────────────────────────────
        // Fatal error sonrası durmuş olabilir, startLoad ile tekrar başlat
        if (this.hls) {
            try {
                this.hls.startLoad();
                console.log('✅ HLS.js startLoad() çağrıldı');
            } catch (e) {
                console.warn('⚠️ HLS startLoad hatası:', e.message);
            }
        }

        // ── ADIM 2: Audio element'i çal ───────────────────────────
        // isPlaying=true ama audio duruyorsa (offline sırasında durdu)
        const audio = this.getActiveHlsAudio?.();
        if (audio && audio.paused && this.isPlaying) {
            try {
                await audio.play();
                console.log('✅ Audio play() başarılı (online recovery)');
            } catch (e) {
                // Autoplay policy veya token expired → şarkıyı yeniden yükle
                console.warn('⚠️ Audio play() başarısız, şarkı yeniden yüklenecek:', e.message);

                if (this.currentSong) {
                    // refreshHlsStream varsa token yenile, yoksa playSong ile yeniden başlat
                    if (typeof this.refreshHlsStream === 'function') {
                        await this.refreshHlsStream();
                    } else {
                        // Şarkıyı kaldığı yerden yeniden başlat
                        const savedTime = this.currentTime || 0;
                        await this.playSong(this.queueIndex, true);
                        // İsteğe bağlı: kaldığı yerden devam et
                        // const a = this.getActiveHlsAudio?.();
                        // if (a && savedTime > 0) a.currentTime = savedTime;
                    }
                }
            }
        }

        // ── ADIM 3: Buffer boşsa şarkıyı yeniden yükle ───────────
        // startLoad çalışsa bile buffer sıfır olabilir
        const buffered = this.getBufferedAmount?.() ?? 0;
        if (buffered < 2 && this.currentSong && !audio?.src) {
            console.log('⚠️ Buffer boş, şarkı yeniden yükleniyor...');
            if (typeof this.refreshHlsStream === 'function') {
                await this.refreshHlsStream();
            } else {
                await this.playSong(this.queueIndex, true);
            }
        }

        // ── ADIM 4: Queue doldur ──────────────────────────────────
        // (mevcut mantık — değişmedi)
        await this.checkAndRefillQueue();

    }, 1500); // 1.5sn: bağlantı stabilize olsun (eskiden 1sn)
});

Bonus fix — _networkOffline başlangıç değeri

Şu an Alpine store'da bu değişken tanımsız (undefined) başlıyor. Sayfa internet kesiliyken açılırsa offline eventi hiç ateşlenmez ve flag set edilmez. Küçük ama tamamlayıcı bir fix:

Konum: Alpine store tanımı içinde (satır ~756 civarı, diğer _ ile başlayan property'lerin yanına)

// MEVCUT — tanımsız, undefined olarak başlıyor
_preloadedNext: null,
_preloadNextInProgress: false,
// ... _networkOffline YOK
// FIX — başlangıç değeri ekle
_preloadedNext: null,
_preloadNextInProgress: false,
_networkOffline: !navigator.onLine,  // ← bunu ekle

Fix sonrası ne değişir?

Kısa kesinti (30sn–2dk): Buffer'da hâlâ müzik var, HLS.js startLoad ile devam eder, audio.play() çağrılır → müzik durmadan devam eder.

Uzun kesinti (5–15dk): Buffer boş, token dolmuş. refreshHlsStream veya playSong yeni token ile şarkıyı yeniden yükler → müzik kaldığı yerden değil ama yeniden başlar.

Her iki durumda: El müdahalesi gerekmez. 16 saatlik kesintisiz çalma güvencesi sağlanır.

📋 Fix Sonrası Kontrol Listesi