22 Şubat 2026 • v3 • Muzibu.com.tr
Durum (v3): 5 bug bulunup düzeltildi, 66 şarkı yeniden encode edildi, ABA testleri yapıldı. Ancak sorun henüz tam olarak çözülmüş değil — testler devam ediyor. Bu rapor yapılan tüm işlemlerin kaydıdır.
Müzik dinlerken şarkılar en düşük kalitede çalıyordu. İnternet hızı ne kadar iyi olursa olsun, herkes maksimum 64kbps (telefon kalitesinin bile altında) ses duyuyordu. Orijinal kalite hiç çalmıyordu.
Şarkıları korumak için kullandığımız şifreleme sisteminde bir hata vardı. Orijinal kaliteli dosyaların "şifre kombinasyonu" (IV değeri) sıfır olarak yazılmıştı. Bu yüzden orijinal kalite çözülemiyordu ve biz de onu tamamen kapattık. Sonuç: herkes düşük kalitede dinliyordu.
Dosya: Modules/Muzibu/App/Jobs/ConvertToHLSJob.php:95-98
// ESKİ KOD (HATALI):
$keyInfoContent = $keyUri . "\n" . $keyPath . "\n";
// 3. satır (IV) yazılmamış! FFmpeg IV=0x0000...0000 kullanıyor
// YENİ KOD (DÜZELTİLMİŞ):
$iv = bin2hex(random_bytes(16));
$keyInfoContent = $keyUri . "\n" . $keyPath . "\n" . $iv;
// Artık her şarkı benzersiz IV alıyor
Neden önemli: AES-128'de IV her dosya için benzersiz olmalı. Sıfır IV hem güvenlik açığı hem de HLS.js'te "decryptdata unset or changed" hatasının ana nedeni.
Dosya: app/Services/Muzibu/HLSService.php:509
// ESKİ KOD (HATALI):
$highBitrate = round($totalSize / ($segCount * self::CHUNK_DURATION) * 8);
// CHUNK_DURATION=4sn ama orijinal segmentler 10sn → hesap 2.5x şişik
// 268kbps yerine 669kbps yazıyordu
// YENİ KOD (DÜZELTİLMİŞ):
$playlistContent = file_get_contents($highPlaylist);
preg_match_all('/#EXTINF:([\d.]+),/', $playlistContent, $matches);
foreach ($matches[1] as $dur) { $totalDuration += (float) $dur; }
$highBitrate = round($totalSize * 8 / $totalDuration);
// Playlist'ten gerçek süre okunuyor
Dosya: Modules/Muzibu/App/Http/Controllers/Api/SongStreamController.php:746-755
// ESKİ KOD: master.m3u8'den orijinal kaliteyi tamamen siliyordu
$content = preg_replace(
'/^#EXT-X-STREAM-INF:[^\n]*\nplaylist\.m3u8\n?/m',
'', $content // HIGH SİLİNİYORDU!
);
// YENİ KOD: Filtre kaldırıldı, yerine:
// 1. High playlist'e query parametreleri ekleniyor
// 2. High key URI'sine &level=high ekleniyor (race condition önlemi)
// 3. Segmentler relative path olarak kalıyor
Dosya: Modules/Muzibu/App/Http/Controllers/Api/SongStreamController.php
// ESKİ KOD (HATALI):
// Segment URL'leri /storage/tenant1001/... şeklinde rewrite ediliyordu
// AMA storage klasörü direkt erişime kapalı → 403 Forbidden
$line = str_replace('segment-', $storageUrl . '/segment-', $line);
// YENİ KOD (DÜZELTİLMİŞ):
// Segment URL'leri relative kalıyor — HLS.js playlist URL'sine göre çözer
// Segmentler /hls/muzibu/songs/{id}/segment-XXX.ts üzerinden sunuluyor
// Storage direkt erişimi 403 döndüğü için rewrite YAPMIYORUZ
Şarkı parçacıkları (segment) doğrudan storage klasöründen istenince sunucu "Erişim Yasak" (403) diyordu. Çözüm: Parçacık URL'lerini olduğu gibi bırakıyoruz, HLS.js kendisi Laravel üzerinden (/hls/ endpoint) erişiyor.
Dosya: public/themes/muzibu/js/player/core/player-core.js
// ESKİ KOD:
startLevel: 0, // En düşük kaliteden başla (ultralow 32kbps)
abrEwmaDefaultEstimate: 64000, // 64kbps bant tahmini
this.hls.autoLevelCapping = 0; // Hardcoded ultralow lock
// YENİ KOD:
startLevel: 2, // Direkt orijinal (high) kaliteden başla
abrEwmaDefaultEstimate: 250000, // 250kbps bant tahmini (orijinale uygun)
var _startLvl = this.hls.config.startLevel || 2;
this.hls.autoLevelCapping = _startLvl; // Dinamik start level
Player eskiden en kötü kaliteden başlayıp yavaş yavaş yükseltiyordu. İlk birkaç saniye herkes kötü ses duyuyordu. Şimdi direkt orijinal kaliteden başlıyor — orijinal ~200kbps olduğu için herhangi bir internet bağlantısı kaldırır. Eğer bağlantı çok kötüyse ABR otomatik düşürür.
| Konum | Eski | Yeni |
|---|---|---|
| HLS_SHARED_CONFIG | startLevel: 0 | startLevel: 2 |
| HLS_SHARED_CONFIG | abrEwmaDefaultEstimate: 64000 | 250000 |
| playHlsStream (ana) | autoLevelCapping = 0 | config.startLevel || 2 |
| Crossfade | autoLevelCapping = 0 | config.startLevel || 2 |
| Recovery | autoLevelCapping = 0 | config.startLevel || 2 |
| Preload | autoLevelCapping = 0 | config.startLevel || 2 |
| Reconnect | autoLevelCapping = 0 | config.startLevel || 2 |
Not: Düşük profilli cihazlar (eski telefon/yavaş bağlantı) hala startLevel:0 kullanıyor — getAdaptiveHlsConfig() ile override ediliyor.
storage/tenant{ID}/app/public/muzibu/hls/{song_id}/
├── enc.bin ← 16 byte AES-128 key (TÜM kaliteler paylaşır)
├── enc.keyinfo ← FFmpeg keyinfo (key URL + key path + IV)
├── master.m3u8 ← Ana playlist (3 kaliteyi listeler)
├── playlist.m3u8 ← Orijinal kalite playlist
├── segment-000.ts ← Orijinal kalite segmentleri (6sn)
├── segment-001.ts
├── ...
├── ultralow/
│ ├── enc.keyinfo ← Farklı IV!
│ ├── playlist.m3u8
│ ├── segment-000.ts ← 32kbps segmentler (4sn)
│ └── ...
└── low/
├── enc.keyinfo ← Farklı IV!
├── playlist.m3u8
└── segment-000.ts ← 64kbps segmentler (4sn)
Satır 1: Key URI → https://domain.com/storage/tenant{ID}/muzibu/hls/{song_id}/enc.bin
Satır 2: Key Path → /var/www/.../storage/tenant{ID}/app/public/muzibu/hls/{song_id}/enc.bin
Satır 3: IV → a1b2c3d4e5f6789012345678abcdef01 (32 hex char = 16 byte)
Bug: Satır 3 yazılmamıştı! FFmpeg boş satırı görünce IV=0x0000...0000 kullanıyordu.
| Sistem | Dosya | Ne Üretiyor | IV | Segment |
|---|---|---|---|---|
| ConvertToHLSJob | Modules/.../Jobs/ConvertToHLSJob.php |
Orijinal (high) | Eksikti! → 0x00 | 10sn → 6sn |
| HLSService | app/Services/Muzibu/HLSService.php |
Ultralow + Low | random_bytes(16) ✓ | 4sn ✓ |
ConvertToHLSJob Horizon queue üzerinden çalışıyor (ilk upload). HLSService ise variant'ları sonradan ekliyor. İkisi farklı davranıyordu.
| Kalite | Bitrate | Sample Rate | Kanal | Segment | Kullanım |
|---|---|---|---|---|---|
| ultralow | 32 kbps | 22050 Hz | Mono | 4 sn | Çok kötü bağlantı |
| low | 64 kbps | 22050 Hz | Mono | 4 sn | Orta bağlantı |
| high (orijinal) | ~180-280 kbps (MP3'e göre) | 48000 Hz | Stereo | 6 sn | Normal/iyi bağlantı |
Player → master.m3u8 ister → /hls/muzibu/songs/{id}/master.m3u8 → SongStreamController Player → playlist.m3u8 ister → /hls/muzibu/songs/{id}/playlist.m3u8 → SongStreamController Player → segment-000.ts ister → /hls/muzibu/songs/{id}/segment-000.ts → SongStreamController Player → enc.bin key ister → /hls-key/muzibu/songs/{id}?... → SongController
Kritik: Tüm dosyalar Laravel üzerinden sunuluyor. /storage/ klasörüne direkt erişim 403 döner — bu yüzden segment URL'leri relative kalmalı!
ffmpeg -i {MP3_PATH} \ -map 0:a \ -c:a aac \ -b:a {TARGET_BITRATE} \ -profile:a aac_low \ -ar 48000 \ -ac 2 \ -vn \ -hls_key_info_file {HLS_DIR}/enc.keyinfo \ -start_number 0 \ -hls_time 6 \ -hls_list_size 0 \ -hls_segment_filename {HLS_DIR}/segment-%03d.ts \ -f hls \ {HLS_DIR}/playlist.m3u8 -y
Ana sunucuda (muzibu.com) uygulanacak adımlar. Test sunucusunda (mztest) 66 şarkıda başarıyla test edildi.
Önce kodu düzelt ki yeni encode edilen şarkılar doğru olsun:
Modules/Muzibu/App/Jobs/ConvertToHLSJob.php
→ IV eklendi + hls_time 10→6
app/Services/Muzibu/HLSService.php
→ Bitrate hesabı düzeltildi
Modules/.../SongStreamController.php
→ High filtre kaldırıldı + segment rewrite kaldırıldı
public/themes/muzibu/js/player/core/player-core.js
→ startLevel:2 + dinamik autoLevelCapping
Tinker ile HLS'i olan tüm şarkıları dosyaya yaz:
php artisan tinker --execute="
\$tenant = App\Models\Tenant::find(1001);
tenancy()->initialize(\$tenant);
\$songs = DB::connection('tenant')->table('muzibu_songs')
->whereNotNull('hls_path')->get(['song_id','file_path']);
foreach(\$songs as \$s) {
echo \$s->song_id . '|' . \$s->file_path . PHP_EOL;
}
" > /tmp/hls-songs.txt
Not: Bu script sadece orijinal (high) kaliteyi yeniden encode eder. Ultralow ve low'a dokunmaz. Zaten doğru IV'si olan şarkıları atlar.
#!/bin/bash # re-encode-high.sh — Orijinal kaliteyi IV + 6sn segment ile yeniden encode et HLS_BASE="/path/to/storage/tenant{ID}/app/public/muzibu/hls" SONGS_BASE="/path/to/storage/tenant{ID}/app/public/muzibu/songs" DOMAIN="muzibu.com" TENANT_ID="1001" SONG_LIST="/tmp/hls-songs.txt" SUCCESS=0; FAIL=0; SKIP=0; TOTAL=0 for dir in "$HLS_BASE"/*/; do SONG_ID=$(basename "$dir") PL="$dir/playlist.m3u8" # Sadece IV=0x00 olanları işle (zaten doğru olanları atla) [ ! -f "$PL" ] && continue grep -q "IV=0x00000000" "$PL" || { SKIP=$((SKIP+1)); continue; } TOTAL=$((TOTAL + 1)) # MP3 dosyasını bul MP3_FILE=$(grep "^${SONG_ID}|" "$SONG_LIST" | cut -d'|' -f2) MP3_PATH="$SONGS_BASE/$MP3_FILE" [ -z "$MP3_FILE" ] || [ ! -f "$MP3_PATH" ] && { FAIL=$((FAIL+1)); continue; } KEY_PATH="$dir/enc.bin" [ ! -f "$KEY_PATH" ] && { FAIL=$((FAIL+1)); continue; } # Eski orijinal segmentleri sil rm -f "$dir"/segment-*.ts "$dir/playlist.m3u8" # Yeni IV oluştur ve enc.keyinfo'yu güncelle NEW_IV=$(openssl rand -hex 16) KEY_URI="https://$DOMAIN/storage/tenant$TENANT_ID/muzibu/hls/$SONG_ID/enc.bin" printf '%s\n%s\n%s' "$KEY_URI" "$KEY_PATH" "$NEW_IV" > "$dir/enc.keyinfo" # Orijinal bitrate'i tespit et BR=$(/usr/bin/ffprobe -v quiet -show_entries format=bit_rate \ -of csv=p=0 "$MP3_PATH" 2>/dev/null) BRK=$((BR / 1000)) # Bitrate mapping if [ "$BRK" -le 128 ]; then T="128k" elif [ "$BRK" -le 160 ]; then T="160k" elif [ "$BRK" -le 192 ]; then T="192k" elif [ "$BRK" -le 256 ]; then T="256k" elif [ "$BRK" -le 320 ]; then T="320k" else T="${BRK}k"; fi # FFmpeg ile yeniden encode (6sn segment, IV ile) /usr/bin/ffmpeg -i "$MP3_PATH" \ -map 0:a -c:a aac -b:a "$T" -profile:a aac_low \ -ar 48000 -ac 2 -vn \ -hls_key_info_file "$dir/enc.keyinfo" \ -start_number 0 -hls_time 6 -hls_list_size 0 \ -hls_segment_filename "$dir/segment-%03d.ts" \ -f hls "$dir/playlist.m3u8" -y 2>/dev/null [ $? -ne 0 ] && { FAIL=$((FAIL+1)); continue; } # Master playlist'i yeniden oluştur (doğru bitrate ile) TD=$(grep '#EXTINF:' "$dir/playlist.m3u8" | \ sed 's/#EXTINF://' | sed 's/,$//' | \ python3 -c "import sys; print(sum(float(l) for l in sys.stdin))") TS=$(du -cb "$dir"/segment-*.ts 2>/dev/null | tail -1 | cut -f1) HBW=$(python3 -c "print(int(round($TS * 8 / $TD)))") M="$dir/master.m3u8" echo "#EXTM3U" > "$M" [ -f "$dir/ultralow/playlist.m3u8" ] && \ printf '#EXT-X-STREAM-INF:BANDWIDTH=32000,CODECS="mp4a.40.2",NAME="ultralow"\nultralow/playlist.m3u8\n' >> "$M" [ -f "$dir/low/playlist.m3u8" ] && \ printf '#EXT-X-STREAM-INF:BANDWIDTH=64000,CODECS="mp4a.40.2",NAME="low"\nlow/playlist.m3u8\n' >> "$M" [ -f "$dir/mid/playlist.m3u8" ] && \ printf '#EXT-X-STREAM-INF:BANDWIDTH=128000,CODECS="mp4a.40.2",NAME="mid"\nmid/playlist.m3u8\n' >> "$M" printf "#EXT-X-STREAM-INF:BANDWIDTH=${HBW},CODECS=\"mp4a.40.2\",NAME=\"high\"\nplaylist.m3u8\n" >> "$M" SC=$(ls "$dir"/segment-*.ts 2>/dev/null | wc -l) echo "[$TOTAL] $SONG_ID — $T ${SC}seg BW=${HBW}" SUCCESS=$((SUCCESS + 1)) done echo "" echo "Başarılı: $SUCCESS | Atlandı: $SKIP | Başarısız: $FAIL | Toplam: $TOTAL"
# Arka planda çalıştır (nohup ile)
nohup bash re-encode-high.sh > /tmp/re-encode.log 2>&1 &
# İlerlemeyi takip et
tail -f /tmp/re-encode.log
# Tamamlanınca doğrula (sıfır IV kalmış mı?)
for dir in $HLS_BASE/*/; do
PL="$dir/playlist.m3u8"
[ -f "$PL" ] && grep -q "IV=0x00000000" "$PL" && echo "BOZUK: $(basename $dir)"
done
Paralel çalıştırma: GNU parallel veya xargs ile 4 çekirdek kullanılırsa ~10-16 saat'e düşer. Ama CPU yükünü izlemek lazım (canlı sunucu).
# 1. Sıfır IV kalmış mı?
BROKEN=0; OK=0
for dir in $HLS_BASE/*/; do
PL="$dir/playlist.m3u8"; [ ! -f "$PL" ] && continue
grep -q "IV=0x00000000" "$PL" && BROKEN=$((BROKEN+1)) || OK=$((OK+1))
done
echo "OK: $OK | Bozuk: $BROKEN"
# 2. master.m3u8'de high variant var mı?
grep -l "NAME=\"high\"" $HLS_BASE/*/master.m3u8 | wc -l
# 3. Rastgele bir şarkıyı kontrol et
SAMPLE=$(ls -d $HLS_BASE/*/ | shuf -n 1)
echo "=== $(basename $SAMPLE) ==="
grep "EXT-X-KEY" "$SAMPLE/playlist.m3u8" # IV sıfır değil mi?
cat "$SAMPLE/master.m3u8" # 3 kalite var mı?
ls "$SAMPLE"/segment-*.ts | wc -l # Segment sayısı
| Dosya | Değişiklik |
|---|---|
Modules/Muzibu/App/Jobs/ConvertToHLSJob.php |
IV eklendi (random_bytes) + hls_time 10→6 |
app/Services/Muzibu/HLSService.php |
generateMasterPlaylist() bitrate hesabı: EXTINF sürelerinden |
Modules/.../SongStreamController.php |
High filtre kaldırıldı + segment storage rewrite kaldırıldı |
public/.../player-core.js |
startLevel:2 + dinamik autoLevelCapping (7 nokta güncellendi) |
storage/.../hls/*/ |
66 şarkı yeniden encode (test) — 30.000 şarkı bekliyor (production) |
Encode sonrası doğrulama: IV doğru: 66 | IV sıfır: 0
Bitrate aralığı: 192k-256k (MP3 orijinaline bağlı)
Gerçek bandwidth: 210-285 kbps (master.m3u8'de doğru raporlanıyor)
→ Bug 4 (segment 403) bu testte keşfedildi
→ Bug 4 düzeltildikten sonra