/
Dmitry-F
/
cbt_tool
Обзор
Документация
Войти
/
Dmitry-F
/
cbt_tool
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
python-ml-service
app/javascript/controllers/audio_recorder_controller.js
162 строки
5 KB
Dmitriy-Filatov
chore: add distortions.py and update ML service configs
14 апр 2026, 03:46
14 апр 2026, 03:46
f5819c2
Код
Авторство
О чём код?
import { Controller } from "@hotwired/stimulus"; export default class extends Controller { static targets = ["recordButton", "buttonText", "timer", "fileField"]; connect() { this.isRecording = false; this.mediaRecorder = null; this.audioChunks = []; this.startTime = null; this.timerInterval = null; } async toggleRecording() { if (this.isRecording) { await this.stop(); } else { await this.start(); } } async start() { try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); // Добавляем поддержку разных форматов для стабильности на Android const mimeType = MediaRecorder.isTypeSupported("audio/webm") ? "audio/webm" : "audio/ogg"; this.mediaRecorder = new MediaRecorder(stream, { mimeType }); this.audioChunks = []; this.mediaRecorder.ondataavailable = (event) => { if (event.data.size > 0) { this.audioChunks.push(event.data); } }; this.mediaRecorder.onstop = () => { const audioBlob = new Blob(this.audioChunks, { type: mimeType }); this.attachToForm(audioBlob); this.showSuccessState(); }; // Просим рекордер отдавать данные каждые 1000мс (стабильнее для мобилок) this.mediaRecorder.start(1000); this.isRecording = true; this.startTimer(); this.updateUI(true); } catch (err) { console.error("Ошибка доступа к микрофону:", err); alert("Микрофон недоступен. Проверьте разрешения в браузере."); } } // Универсальный метод остановки, который ждет завершения упаковки файла stop() { return new Promise((resolve) => { if (this.mediaRecorder && this.isRecording) { // Перезаписываем onstop, чтобы он вызвал resolve const originalOnStop = this.mediaRecorder.onstop; this.mediaRecorder.onstop = () => { originalOnStop(); resolve(); }; this.mediaRecorder.stop(); this.isRecording = false; this.stopTimer(); this.updateUI(false); this.mediaRecorder.stream.getTracks().forEach((track) => track.stop()); } else { resolve(); } }); } attachToForm(blob) { if (!blob || blob.size === 0) return; const file = new File([blob], "recording.webm", { type: blob.type }); const dataTransfer = new DataTransfer(); dataTransfer.items.add(file); if (this.hasFileFieldTarget) { this.fileFieldTarget.files = dataTransfer.files; console.log("Аудио файл успешно прикреплен к форме. Размер:", blob.size); } } async validateForm(event) { // Если пользователь нажал "Сохранить", пока идет запись if (this.isRecording) { event.preventDefault(); // Останавливаем отправку const form = event.target; await this.stop(); // Ждем корректной остановки и прикрепления файла // Отправляем форму вручную, когда файл уже точно на месте form.submit(); } } startTimer() { this.startTime = Date.now(); this.timerTarget.classList.remove("hidden"); this.timerInterval = setInterval(() => { const elapsed = Math.floor((Date.now() - this.startTime) / 1000); const minutes = Math.floor(elapsed / 60) .toString() .padStart(2, "0"); const seconds = (elapsed % 60).toString().padStart(2, "0"); this.timerTarget.innerText = `${minutes}:${seconds}`; }, 1000); } stopTimer() { clearInterval(this.timerInterval); this.timerTarget.classList.add("hidden"); this.timerTarget.innerText = "00:00"; } updateUI(recording) { if (recording) { this.recordButtonTarget.classList.add( "bg-red-500", "text-white", "border-red-600", ); this.recordButtonTarget.classList.remove("bg-slate-50", "text-slate-500"); this.buttonTextTarget.innerText = "ОСТАНОВИТЬ"; } else { this.recordButtonTarget.classList.remove( "bg-red-500", "text-white", "border-red-600", ); this.recordButtonTarget.classList.add("bg-slate-50", "text-slate-500"); this.buttonTextTarget.innerText = "ЗАПИСАТЬ ГОЛОС"; } } showSuccessState() { this.buttonTextTarget.innerText = "✅ ГОТОВО"; this.recordButtonTarget.classList.add( "bg-green-50", "text-green-600", "border-green-200", ); setTimeout(() => { if (!this.isRecording) { this.buttonTextTarget.innerText = "ЗАПИСАТЬ ЗАНОВО"; this.recordButtonTarget.classList.remove( "bg-green-50", "text-green-600", "border-green-200", ); } }, 3000); } }