Spaces:
Runtime error
Runtime error
File size: 7,483 Bytes
b4e1f3c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 | (function () {
function init() {
/* ---- refs ---- */
const micBtn = document.getElementById('lumi-mic-btn');
if (!micBtn) return false; // DOM chưa sẵn sàng, thử lại sau
const recLabel = document.getElementById('lumi-rec-label');
const canvas = document.getElementById('lumi-canvas');
const ctx2d = canvas.getContext('2d');
const procBar = document.getElementById('lumi-proc-bar');
const playWrap = document.getElementById('lumi-audio-playback');
const audioEl = document.getElementById('lumi-audio-el');
/* ---- state ---- */
let isRecording = false;
let mediaRecorder = null;
let audioChunks = [];
let audioCtx = null;
let analyser = null;
let micStream = null;
let animId = null;
/* ---- mic button ---- */
micBtn.addEventListener('click', () => {
isRecording ? stopRecording() : startRecording();
});
/* ========================
RECORDING
======================== */
async function startRecording() {
try {
micStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
} catch (e) {
recLabel.textContent = '❌ Không có quyền truy cập micro!';
return;
}
// Web Audio → visualizer
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
analyser = audioCtx.createAnalyser();
analyser.fftSize = 128;
audioCtx.createMediaStreamSource(micStream).connect(analyser);
// MediaRecorder → lưu blob
audioChunks = [];
const mime = ['audio/webm;codecs=opus', 'audio/webm', 'audio/ogg']
.find(t => MediaRecorder.isTypeSupported(t)) || '';
mediaRecorder = new MediaRecorder(micStream, mime ? { mimeType: mime } : {});
mediaRecorder.ondataavailable = e => { if (e.data.size > 0) audioChunks.push(e.data); };
mediaRecorder.onstop = onRecordingDone;
mediaRecorder.start(100);
isRecording = true;
micBtn.classList.add('recording');
micBtn.textContent = '⏹️';
recLabel.textContent = '🔴 Đang ghi... Nhấn để dừng';
recLabel.classList.add('recording');
playWrap.style.display = 'none';
procBar.style.display = 'none';
drawWave();
}
function stopRecording() {
if (mediaRecorder && mediaRecorder.state !== 'inactive') mediaRecorder.stop();
if (micStream) micStream.getTracks().forEach(t => t.stop());
if (audioCtx) audioCtx.close();
if (animId) cancelAnimationFrame(animId);
micStream = audioCtx = analyser = null;
isRecording = false;
micBtn.classList.remove('recording');
micBtn.textContent = '🎙️';
recLabel.textContent = 'Nhấn để ghi âm lại';
recLabel.classList.remove('recording');
procBar.style.display = 'block';
drawFlatLine();
}
function onRecordingDone() {
const mime = audioChunks[0]?.type || 'audio/webm';
const blob = new Blob(audioChunks, { type: mime });
// Playback
const url = URL.createObjectURL(blob);
audioEl.src = url;
audioEl.oncanplay = () => {
procBar.style.display = 'none';
playWrap.style.display = 'flex';
};
injectFileToGradio(blob, mime);
}
/* ========================
INJECT VÀO GRADIO
======================== */
function injectFileToGradio(blob, mime) {
const ext = mime.includes('ogg') ? 'ogg' : 'webm';
const file = new File([blob], 'recording.' + ext, { type: mime });
function attempt(tries) {
const inp = document.querySelector('.hidden-audio-wrap input[type="file"]');
if (inp) {
const dt = new DataTransfer();
dt.items.add(file);
inp.files = dt.files;
inp.dispatchEvent(new Event('change', { bubbles: true }));
} else if (tries > 0) {
setTimeout(() => attempt(tries - 1), 300);
}
}
attempt(10);
}
/* ========================
WAVEFORM VISUALIZER
======================== */
function drawWave() {
if (!analyser) return;
animId = requestAnimationFrame(drawWave);
const buf = analyser.frequencyBinCount;
const data = new Uint8Array(buf);
analyser.getByteTimeDomainData(data);
const W = canvas.width, H = canvas.height;
ctx2d.clearRect(0, 0, W, H);
const rms = Math.sqrt(data.reduce((s, v) => s + (v - 128) ** 2, 0) / buf);
const intensity = Math.min(rms / 28, 1);
// Glow rings
if (intensity > 0.04) {
const cx = W / 2, cy = H / 2;
[0, 1, 2].forEach(r => {
ctx2d.beginPath();
ctx2d.arc(cx, cy, 16 + r * 13 + intensity * 18, 0, Math.PI * 2);
ctx2d.strokeStyle = `rgba(167,139,250,${(0.20 - r * 0.05) * intensity})`;
ctx2d.lineWidth = 9 - r * 2.5;
ctx2d.stroke();
});
}
// Waveform
const g = ctx2d.createLinearGradient(0, 0, W, 0);
g.addColorStop(0, 'rgba(124,111,247,0)');
g.addColorStop(0.2, `rgba(124,111,247,${0.45 + intensity * 0.55})`);
g.addColorStop(0.5, `rgba(167,139,250,${0.75 + intensity * 0.25})`);
g.addColorStop(0.8, `rgba(56,189,248,${0.45 + intensity * 0.55})`);
g.addColorStop(1, 'rgba(56,189,248,0)');
ctx2d.beginPath();
ctx2d.lineWidth = 2.5;
ctx2d.strokeStyle = g;
ctx2d.shadowColor = '#7c6ff7';
ctx2d.shadowBlur = 10 * intensity;
const sw = W / buf;
for (let i = 0; i < buf; i++) {
const y = ((data[i] / 128) * H) / 2;
i === 0 ? ctx2d.moveTo(0, y) : ctx2d.lineTo(i * sw, y);
}
ctx2d.stroke();
ctx2d.shadowBlur = 0;
}
function drawFlatLine() {
ctx2d.clearRect(0, 0, canvas.width, canvas.height);
ctx2d.beginPath();
ctx2d.moveTo(0, canvas.height / 2);
ctx2d.lineTo(canvas.width, canvas.height / 2);
ctx2d.strokeStyle = 'rgba(124,111,247,0.18)';
ctx2d.lineWidth = 1.5;
ctx2d.stroke();
}
drawFlatLine(); // init
return true;
}
/* ========================
ĐỢI DOM SẴN SÀNG
Thử init ngay, nếu chưa được thì
MutationObserver sẽ thử lại mỗi khi DOM thay đổi
======================== */
if (!init()) {
const observer = new MutationObserver(() => {
if (init()) observer.disconnect();
});
observer.observe(document.body, { childList: true, subtree: true });
}
})(); |