v63: кликабельные слоги — проигрывание сегментов аудио
This commit is contained in:
+48
-1
@@ -53,9 +53,38 @@ let mediaRecorder, audioChunks = [], userAudioBlob = null, ttsText = '';
|
|||||||
let isRecording = false, isAnalyzing = false, micAnalyser = null, micAnimId = null;
|
let isRecording = false, isAnalyzing = false, micAnalyser = null, micAnimId = null;
|
||||||
let recordStartTime = 0, recordDuration = 0;
|
let recordStartTime = 0, recordDuration = 0;
|
||||||
// ---------- Версия ----------
|
// ---------- Версия ----------
|
||||||
const VERSION = 'v62';
|
const VERSION = 'v63';
|
||||||
document.getElementById('ver').textContent = VERSION;
|
document.getElementById('ver').textContent = VERSION;
|
||||||
|
|
||||||
|
// ---------- AudioContext для проигрывания слогов ----------
|
||||||
|
let _audioCtx = null;
|
||||||
|
function getAudioCtx() {
|
||||||
|
if (!_audioCtx) _audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
||||||
|
if (_audioCtx.state === 'suspended') _audioCtx.resume();
|
||||||
|
return _audioCtx;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _currentBlob = null; // blob для проигрывания слогов
|
||||||
|
let _currentSylBuf = null; // декодированный AudioBuffer
|
||||||
|
|
||||||
|
async function playSyllable(sylIdx, total) {
|
||||||
|
if (!_currentBlob) return;
|
||||||
|
const ctx = getAudioCtx();
|
||||||
|
try {
|
||||||
|
if (!_currentSylBuf) {
|
||||||
|
const ab = await _currentBlob.arrayBuffer();
|
||||||
|
_currentSylBuf = await ctx.decodeAudioData(ab);
|
||||||
|
}
|
||||||
|
const dur = _currentSylBuf.duration;
|
||||||
|
const start = (dur / total) * sylIdx;
|
||||||
|
const len = dur / total;
|
||||||
|
const src = ctx.createBufferSource();
|
||||||
|
src.buffer = _currentSylBuf;
|
||||||
|
src.connect(ctx.destination);
|
||||||
|
src.start(0, start, len);
|
||||||
|
} catch(e) { console.log('playSyllable ERR', e); }
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- Перевод на русский (Groq) ----------
|
// ---------- Перевод на русский (Groq) ----------
|
||||||
async function translateToRussian(text) {
|
async function translateToRussian(text) {
|
||||||
if (!GROQ_API_KEY) return;
|
if (!GROQ_API_KEY) return;
|
||||||
@@ -266,6 +295,24 @@ async function doCompare(blob, originalText, duration) {
|
|||||||
'<span>� Артикуляция: ' + phon.voiceClarity + '%</span>' +
|
'<span>� Артикуляция: ' + phon.voiceClarity + '%</span>' +
|
||||||
'</div>';
|
'</div>';
|
||||||
|
|
||||||
|
// Слоги — кликабельные для прослушивания фрагментов
|
||||||
|
_currentBlob = blob;
|
||||||
|
_currentSylBuf = null;
|
||||||
|
const sy = syllabifyIT(originalText);
|
||||||
|
if (sy && sy.includes('-')) {
|
||||||
|
const syls = sy.split('-');
|
||||||
|
const sylHTML = '<div style="margin-top:8px;font-size:1.2em;letter-spacing:2px">' +
|
||||||
|
syls.map((s, i) =>
|
||||||
|
'<span class="syl-chip" data-syl="' + i + '" data-total="' + syls.length + '"' +
|
||||||
|
' style="cursor:pointer;padding:2px 4px;border-radius:4px;transition:0.15s"' +
|
||||||
|
' onmouseover="this.style.background=\'#e0e0ff\'"' +
|
||||||
|
' onmouseout="this.style.background=\'\'"' +
|
||||||
|
' onclick="playSyllable(' + i + ',' + syls.length + ')">' + s + '</span>'
|
||||||
|
).join('<span style="color:#ccc">·</span>') +
|
||||||
|
'</div>';
|
||||||
|
document.getElementById('diff').innerHTML += sylHTML;
|
||||||
|
}
|
||||||
|
|
||||||
setStatus('Готово.');
|
setStatus('Готово.');
|
||||||
isAnalyzing = false;
|
isAnalyzing = false;
|
||||||
return whisperResult.text || '';
|
return whisperResult.text || '';
|
||||||
|
|||||||
Reference in New Issue
Block a user