Dinamik Senkronizasyon Global Kapatma İçerik Sync 5 Ocak 2026 • v3

Spot Ayarları ve İçerik Dinamik Senkronizasyon v3

Ayarlar + Anons İçeriği + Sıralama + Aktif/Pasif + Arşiv - Tam Otomatik Senkronizasyon

Basit Anlatım (Herkes İçin)

v2'ye ek olarak ne sorun var?

  • Ana şube anonsun ismini değiştiriyor → Şubeler eski ismi görüyor
  • Anons sıralaması değişiyor → Şubeler eski sırayla çalıyor
  • Anons aktif/pasif yapılıyor → Şubeler eski durumu kullanıyor
  • Anons arşivleniyor → Şubeler hala çalmaya çalışıyor
  • Başlangıç/bitiş tarihi değişiyor → Şubeler eski tarihe göre çalıyor
  • Yeni anons ekleniyor → Şubeler görmüyor

v3'te ne yapacağız?

  • Anons içeriği değişince → Tüm şubeler otomatik günceller
  • Sıralama değişince → Rotation sırası yeniden hesaplanır
  • Aktif/Pasif/Arşiv → Player anında uyarlanır
  • Preload'lı spot değişirse → İptal edilir, yenisi yüklenir
  • Yeni anons → 30 saniye içinde tüm şubeler görür

Örnek Senaryo 3:

Ana şube "Yılbaşı Kampanyası" anonsunu arşivler. 30 saniye içinde tüm şubelerin player'ları bu anosu atlayıp bir sonrakine geçer. Preload'da bu anons varsa iptal edilir!

Version Artırıcı Olaylar (10 Tetikleyici)

Ayar Değişiklikleri (3)

  • 1 spot_enabled değişir
  • 2 spot_songs_between değişir
  • 3 spot_current_index sıfırlanır

Anons İçerik (4)

  • 4 title değişir
  • 5 starts_at / ends_at değişir
  • 6 is_enabled değişir
  • 7 is_archived değişir

CRUD İşlemleri (2)

  • 8 Yeni anons eklenir
  • 9 Anons silinir

Sıralama (1)

  • 10 position değişir (reorder)

⚡ Otomatik Tetikleme:

Bu 10 olaydan herhangi biri gerçekleşince spot_settings_version otomatik artırılır. Laravel Observer kullanarak tüm değişiklikler yakalanır.

Teknik Detaylar (Geliştiriciler İçin)

Observer Pattern (Otomatik)

1. CorporateSpotObserver Oluştur

Dosya: Modules/Muzibu/app/Observers/CorporateSpotObserver.php

<?php

namespace Modules\Muzibu\App\Observers;

use Modules\Muzibu\App\Models\CorporateSpot;
use Illuminate\Support\Facades\DB;

class CorporateSpotObserver
{
    /**
     * Anons oluşturulduğunda
     */
    public function created(CorporateSpot $spot)
    {
        $this->incrementVersion($spot);
    }

    /**
     * Anons güncellendiğinde
     * İzlenen field'ler: title, starts_at, ends_at, is_enabled, is_archived, position
     */
    public function updated(CorporateSpot $spot)
    {
        // Sadece önemli field'ler değiştiyse version artır
        $trackedFields = ['title', 'starts_at', 'ends_at', 'is_enabled', 'is_archived', 'position'];

        foreach ($trackedFields as $field) {
            if ($spot->isDirty($field)) {
                $this->incrementVersion($spot);
                break;
            }
        }
    }

    /**
     * Anons silindiğinde
     */
    public function deleted(CorporateSpot $spot)
    {
        $this->incrementVersion($spot);
    }

    /**
     * Version'ı artır
     */
    protected function incrementVersion(CorporateSpot $spot)
    {
        DB::table('muzibu_corporate_accounts')
            ->where('id', $spot->corporate_account_id)
            ->update([
                'spot_settings_version' => DB::raw('spot_settings_version + 1'),
                'updated_at' => now()
            ]);
    }
}

2. Observer'ı Kaydet

Dosya: Modules/Muzibu/Providers/MuzibuServiceProvider.php

use Modules\Muzibu\App\Models\CorporateSpot;
use Modules\Muzibu\App\Observers\CorporateSpotObserver;

public function boot()
{
    // ...

    // ✅ Observer kaydet
    CorporateSpot::observe(CorporateSpotObserver::class);
}

3. Reorder İşleminde Version Artır

Dosya: CorporateFrontController::reorderSpots()

public function reorderSpots(Request $request)
{
    // ... (mevcut sıralama kodu)

    // ✅ YENİ: Sıralama değişti, version artır
    $account->update([
        'spot_settings_version' => DB::raw('spot_settings_version + 1')
    ]);

    return response()->json([
        'success' => true,
        'message' => 'Sıralama güncellendi.',
        'spot_settings_version' => $account->fresh()->spot_settings_version
    ]);
}

API Response (Spot Listesi Ekle)

API: Spot Listesini Dön

Dosya: Modules/Muzibu/app/Http/Controllers/Api/SpotController.php

public function settings(Request $request)
{
    // ... (mevcut kod)

    // ✅ YENİ: Spot listesini de dön (aktif, sıralı)
    $spots = CorporateSpot::where('corporate_account_id', $parentAccount->id)
        ->currentlyActive()  // is_enabled=true, is_archived=false, tarih kontrolü
        ->ordered()          // position ASC
        ->get(['id', 'title', 'slug', 'audio_url', 'duration', 'position', 'starts_at', 'ends_at'])
        ->map(function($spot) {
            return [
                'id' => $spot->id,
                'title' => $spot->title,
                'slug' => $spot->slug,
                'audio_url' => $spot->getAudioUrl(),
                'duration' => $spot->duration,
                'position' => $spot->position,
                'starts_at' => $spot->starts_at?->toISOString(),
                'ends_at' => $spot->ends_at?->toISOString(),
            ];
        });

    return response()->json([
        'enabled' => $effectiveEnabled,
        'spot_enabled' => $parentAccount->spot_enabled,
        'spot_is_paused' => $account->spot_is_paused,
        'songs_between' => $parentAccount->spot_songs_between ?? 10,
        'corporate_id' => $parentAccount->id,
        'branch_id' => $account->id,
        'spot_settings_version' => $parentAccount->spot_settings_version ?? 1,
        'spots' => $spots,  // ✅ YENİ: Spot listesi
    ]);
}

JavaScript: Spot Listesi + Preload İptali

JavaScript: checkSettingsUpdate() Genişlet

Dosya: public/themes/muzibu/js/player/features/spot-player.js

const state = {
    enabled: false,
    spotEnabled: true,
    isPaused: false,
    songsBetween: 10,
    songsPlayed: 0,
    currentVersion: 1,
    checkInterval: null,
    spotsList: [],               // ✅ YENİ: Spot listesi
    preloadedSpot: null,
    preloadedAudio: null,
};

async function checkSettingsUpdate() {
    try {
        const response = await fetch('/api/spot/settings');
        const data = await response.json();

        // Version farklıysa güncelle!
        if (data.spot_settings_version !== state.currentVersion) {
            console.log('🎙️ SpotPlayer: Settings/Spots updated! Version:', data.spot_settings_version);

            // ✅ Ayarları güncelle
            state.songsBetween = data.songs_between || 10;
            state.spotEnabled = data.spot_enabled;
            state.isPaused = data.spot_is_paused;
            state.enabled = data.enabled;
            state.currentVersion = data.spot_settings_version;

            // ✅ YENİ: Spot listesini güncelle
            state.spotsList = data.spots || [];

            // ✅ Preloaded spot geçersiz mi kontrol et
            if (state.preloadedSpot) {
                const stillValid = state.spotsList.find(s => s.id === state.preloadedSpot.id);
                if (!stillValid) {
                    console.log('🎙️ SpotPlayer: Preloaded spot is no longer valid, clearing...');
                    clearPreload(true);  // src'yi de sil
                }
            }

            // Sayacı sıfırla
            resetCounter();

            // UI güncelle
            window.dispatchEvent(new CustomEvent('spot-settings-updated', {
                detail: {
                    songsBetween: state.songsBetween,
                    spotEnabled: state.spotEnabled,
                    isPaused: state.isPaused,
                    enabled: state.enabled,
                    spots: state.spotsList  // ✅ YENİ
                }
            }));
        }
    } catch (e) {
        console.error('🎙️ SpotPlayer: Failed to check settings', e);
    }
}

// ✅ YENİ: playNextSpot() - Liste kullan
async function playNextSpot() {
    if (!state.enabled || state.spotsList.length === 0) {
        console.log('🎙️ SpotPlayer: No spots available');
        resetCounter();
        return null;
    }

    // Rotation: Sıradaki spot
    const currentIndex = state.currentSpot ?
        state.spotsList.findIndex(s => s.id === state.currentSpot.id) : -1;
    const nextIndex = (currentIndex + 1) % state.spotsList.length;
    const spot = state.spotsList[nextIndex];

    state.currentSpot = spot;
    state.wasSkipped = false;
    state.spotStartTime = Date.now();

    await logPlayStart(spot.id);
    resetCounter();

    console.log('🎙️ SpotPlayer: Playing spot:', spot.title);
    return spot;
}

Uygulama Adımları

1

CorporateSpotObserver Oluştur

Anons değişikliklerini otomatik yakala

2

Observer'ı ServiceProvider'a Kaydet

MuzibuServiceProvider::boot()

3

Reorder İşleminde Version Artır

CorporateFrontController::reorderSpots()

4

API'ye Spot Listesi Ekle

SpotController::settings() - Aktif spot listesini dön

5

JavaScript'i Güncelle

spot-player.js - Spot listesi sync + preload invalidation

6

Test Et (10 Senaryo)

  • • Anons ekle → 30s içinde tüm şubeler görür
  • • Anons sil → 30s içinde kaldırılır
  • • İsim değiştir → 30s içinde yeni isim görünür
  • • Sıralama değiştir → 30s içinde yeni sıra uygulanır
  • • Aktif/Pasif → 30s içinde anons atlanır/eklenir
  • • Arşivle → 30s içinde anons kaldırılır
  • • Tarih değiştir → 30s içinde tarih kontrolü güncellenir
  • • Preloaded spot'u sil → Preload iptal edilir
  • • Global kapat → Tüm şubeler durur
  • • Çalma aralığı değiştir → Sayaç sıfırlanır

Önemli Notlar

Versiyon Karşılaştırması

Özellik v1 v2 v3
Çalma Aralığı Sync
Aktif/Pasif Sync
Ana Şube Global Kapatma
Anons İçerik Sync
Sıralama Sync
Anons Ekle/Sil Sync
Preload Invalidation
Observer Pattern

v3 = Tam Dinamik Senkronizasyon

Ayarlar, anons içeriği, sıralama, CRUD - Her şey otomatik, 30 saniyede senkronize!