// ==UserScript==
// @name Kick & YouTube Audio Maximizer
// @name:tr Kick & YouTube Ses Dengeleyici
// @description:tr Sessiz sesleri yükseltir, yüksek sesleri bastırır.
// @description Boosts quiet sounds, compresses loud peaks.
// @namespace http://tampermonkey.net/
// @version 1.6
// @author baris
// @match *://*.kick.com/*
// @match *://kick.com/*
// @match *://*.youtube.com/*
// @match *://youtube.com/*
// @match *://youtu.be/*
// @grant none
// @license GNU GPLv3
// ==/UserScript==
(() => {
// --- DEĞİŞİKLİK 1: Arayüz ve Ses Bağlamını global değişkenlerde tutalım ---
// Bu değişkenler sayesinde UI ve AudioContext sadece bir kez oluşturulacak.
let ui = null;
let audioCtx = null;
// Yeni video yüklendiğinde sadece bir kez init et
async function initVideo() {
const video = document.querySelector('video');
// Eğer video yoksa veya bu videoya zaten işlem uygulandıysa çık
if (!video || video.dataset.maxApplied) return;
video.dataset.maxApplied = '1';
// --- DEĞİŞİKLİK 2: AudioContext'i sadece bir kez oluştur ---
if (!audioCtx) {
audioCtx = new AudioContext();
}
// Kullanıcı etkileşimi olmadan ses başlamazsa, devam ettirmek için dinleyici ekle
if (audioCtx.state === 'suspended') {
const resume = () => { audioCtx.resume(); window.removeEventListener('click', resume); };
window.addEventListener('click', resume);
}
const src = audioCtx.createMediaElementSource(video);
const comp = audioCtx.createDynamicsCompressor();
comp.threshold.value = -50;
comp.knee.value = 30;
comp.ratio.value = 20;
comp.attack.value = 0.002;
comp.release.value = 0.10;
const gain = audioCtx.createGain();
gain.gain.value = 2.5;
const ana = audioCtx.createAnalyser();
ana.fftSize = 512;
const buf = new Uint8Array(ana.fftSize);
src.connect(comp);
comp.connect(gain);
gain.connect(ana);
ana.connect(audioCtx.destination);
// --- DEĞİŞİKLİK 3: Arayüzü oluştur veya mevcut olanı getir ---
// Arayüz daha önce oluşturulmadıysa createUI ile oluşturulacak.
const g = await createUI();
const W = g.canvas.width, H = g.canvas.height;
let smooth = 0, peak = 0, peakT = 0;
const lerp = (a, b, f) => a + (b - a) * f;
(function draw() {
// Eğer video elementi sayfadan kaldırıldıysa (örneğin başka sayfaya geçildi),
// çizim döngüsünü durdur. Bu, gereksiz işlemci kullanımını önler.
if (!video.isConnected) {
return;
}
requestAnimationFrame(draw);
ana.getByteTimeDomainData(buf);
const rms = Math.sqrt(buf.reduce((s, v) => s + ((v - 128) / 128) ** 2, 0) / buf.length);
const level = Math.min(rms * gain.gain.value, 1);
smooth = lerp(smooth, level, 0.18);
const now = performance.now();
if (smooth > peak || now - peakT > 2000) { peak = smooth; peakT = now; }
if (now - peakT > 2000) peak = lerp(peak, smooth, 0.05);
const hue = lerp(220, 0, smooth);
g.clearRect(0, 0, W, H);
g.fillStyle = `hsl(${hue} 80% 50%)`;
g.fillRect(0, 0, smooth * W, H);
g.fillStyle = '#fff';
g.fillRect(peak * W - 1, 0, 2, H);
})();
}
// --- DEĞİŞİKLİK 4: Arayüzü "get-or-create" (varsa getir, yoksa oluştur) mantığıyla yeniden yapılandır ---
// Bu fonksiyon artık UI'yi sadece bir kez oluşturur ve her zaman aynı context'i döndürür.
function createUI() {
return new Promise(res => {
// Eğer UI zaten oluşturulmuşsa, mevcut context'i hemen döndür.
if (ui) {
return res(ui.g);
}
const waitDOM = () => document.body
? Promise.resolve()
: new Promise(inner => {
const id = setInterval(() => {
if (document.body) {
clearInterval(id);
inner();
}
}, 200);
});
waitDOM().then(() => {
const wrap = Object.assign(document.createElement('div'), {
style: `
position:fixed;
bottom:80px;
left:20px;
z-index:2147483647;
background:rgba(0,0,0,0.6);
padding:6px 8px;
border-radius:8px;
pointer-events:none;
`.replace(/\s+/g, ' ')
});
wrap.innerHTML = `
<div style="color:#fff;font-family:monospace;margin-bottom:4px">
🔊 Maximizer by Barış
</div>`;
const canvas = Object.assign(document.createElement('canvas'), {
width:300, height:20,
style:'background:#222;border-radius:6px;display:block;'
});
wrap.appendChild(canvas);
document.body.appendChild(wrap);
// Oluşturulan UI elemanlarını ve context'i global değişkende sakla
ui = {
wrap: wrap,
canvas: canvas,
g: canvas.getContext('2d')
};
// Promise'i çözerek context'i döndür
res(ui.g);
});
});
}
// Başlangıç ve dinleyiciler
(async () => {
// Sayfa ilk yüklendiğinde dene
await initVideo();
// YouTube/Kick SPA navigasyonunda yeniden init et
let lastUrl = location.href;
const observerCallback = () => {
if (location.href !== lastUrl) {
lastUrl = location.href;
// Sayfa geçişlerinde videonun yüklenmesi için kısa bir gecikme eklemek faydalıdır
setTimeout(initVideo, 500);
}
};
// URL değişimlerini hem aralıkla hem de MutationObserver ile takip etmek daha sağlamdır.
setInterval(observerCallback, 1000);
// DOM değişimlerinde video yeniden yüklendiyse init et (örneğin, video oynatıcı ilk kez yüklendiğinde)
new MutationObserver(initVideo).observe(document.body, {
childList: true,
subtree: true
});
})();
})();