v76: проигрывание через trimSilence — без щелчка в начале
This commit is contained in:
Vendored
+25
-21
@@ -392,22 +392,33 @@ function stopRecording() {
|
|||||||
if (mediaRecorder && mediaRecorder.state === 'recording') mediaRecorder.stop();
|
if (mediaRecorder && mediaRecorder.state === 'recording') mediaRecorder.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- Воспроизведение ----------
|
// ---------- Воспроизведение (с обрезкой тишины) ----------
|
||||||
|
async function playTrimmed(blob) {
|
||||||
|
const t = await trimSilence(blob);
|
||||||
|
const ctx = getAudioCtx();
|
||||||
|
let buf;
|
||||||
|
if (t) {
|
||||||
|
const offline = new OfflineAudioContext(1, t.data.length, t.sr);
|
||||||
|
buf = offline.createBuffer(1, t.data.length, t.sr);
|
||||||
|
buf.getChannelData(0).set(t.data);
|
||||||
|
} else {
|
||||||
|
const ab = await blob.arrayBuffer();
|
||||||
|
buf = await ctx.decodeAudioData(ab);
|
||||||
|
}
|
||||||
|
const src = ctx.createBufferSource();
|
||||||
|
src.buffer = buf;
|
||||||
|
src.connect(ctx.destination);
|
||||||
|
src.start();
|
||||||
|
return new Promise(resolve => { src.onended = resolve; });
|
||||||
|
}
|
||||||
|
|
||||||
async function playRec(id) {
|
async function playRec(id) {
|
||||||
try {
|
try {
|
||||||
const db = await openRecDB();
|
const db = await openRecDB();
|
||||||
const tx = db.transaction('recs', 'readonly');
|
const tx = db.transaction('recs', 'readonly');
|
||||||
const store = tx.objectStore('recs');
|
const store = tx.objectStore('recs');
|
||||||
const rec = await new Promise(r => { const req = store.get(id); req.onsuccess = () => r(req.result); });
|
const rec = await new Promise(r => { const req = store.get(id); req.onsuccess = () => r(req.result); });
|
||||||
if (rec?.blob) {
|
if (rec?.blob) await playTrimmed(rec.blob);
|
||||||
const url = URL.createObjectURL(rec.blob);
|
|
||||||
const audio = new Audio(url);
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
audio.onended = () => { URL.revokeObjectURL(url); resolve(); };
|
|
||||||
audio.onerror = () => { URL.revokeObjectURL(url); reject(); };
|
|
||||||
audio.play();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch(e) { /* тихо */ }
|
} catch(e) { /* тихо */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -733,7 +744,7 @@ async function doCompare(blob, originalText, duration) {
|
|||||||
<script>
|
<script>
|
||||||
// ---------- Глобальные переменные и инициализация ----------
|
// ---------- Глобальные переменные и инициализация ----------
|
||||||
const GROQ_API_KEY = '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';
|
const GROQ_API_KEY = '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';
|
||||||
const VERSION = 'v75';
|
const VERSION = 'v76';
|
||||||
|
|
||||||
let mediaRecorder, audioChunks = [], userAudioBlob = null, ttsText = '';
|
let mediaRecorder, audioChunks = [], userAudioBlob = null, ttsText = '';
|
||||||
let isRecording = false, isAnalyzing = false, micAnalyser = null, micAnimId = null;
|
let isRecording = false, isAnalyzing = false, micAnalyser = null, micAnimId = null;
|
||||||
@@ -774,19 +785,12 @@ document.getElementById('btnRecord').onclick = async function () {
|
|||||||
// ---------- Воспроизведение своей записи ----------
|
// ---------- Воспроизведение своей записи ----------
|
||||||
document.getElementById('btnPlayUser').onclick = async () => {
|
document.getElementById('btnPlayUser').onclick = async () => {
|
||||||
if (!userAudioBlob) return setStatus('Нет записи!');
|
if (!userAudioBlob) return setStatus('Нет записи!');
|
||||||
if (userAudioBlob.size === 0) return setStatus('Запись пустая!');
|
|
||||||
const url = URL.createObjectURL(userAudioBlob);
|
|
||||||
const audio = new Audio(url);
|
|
||||||
audio.volume = 1.0;
|
|
||||||
audio.onended = () => { URL.revokeObjectURL(url); setStatus('Готово.'); };
|
|
||||||
audio.onerror = () => { URL.revokeObjectURL(url); setStatus('Ошибка воспроизведения.'); };
|
|
||||||
setStatus('🔊 Ваша запись...');
|
setStatus('🔊 Ваша запись...');
|
||||||
try {
|
try {
|
||||||
await audio.play();
|
await playTrimmed(userAudioBlob);
|
||||||
|
setStatus('Готово.');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
URL.revokeObjectURL(url);
|
setStatus('Ошибка воспроизведения.');
|
||||||
setStatus('Ошибка воспроизведения — нажмите ещё раз.');
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+21
-10
@@ -90,22 +90,33 @@ function stopRecording() {
|
|||||||
if (mediaRecorder && mediaRecorder.state === 'recording') mediaRecorder.stop();
|
if (mediaRecorder && mediaRecorder.state === 'recording') mediaRecorder.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- Воспроизведение ----------
|
// ---------- Воспроизведение (с обрезкой тишины) ----------
|
||||||
|
async function playTrimmed(blob) {
|
||||||
|
const t = await trimSilence(blob);
|
||||||
|
const ctx = getAudioCtx();
|
||||||
|
let buf;
|
||||||
|
if (t) {
|
||||||
|
const offline = new OfflineAudioContext(1, t.data.length, t.sr);
|
||||||
|
buf = offline.createBuffer(1, t.data.length, t.sr);
|
||||||
|
buf.getChannelData(0).set(t.data);
|
||||||
|
} else {
|
||||||
|
const ab = await blob.arrayBuffer();
|
||||||
|
buf = await ctx.decodeAudioData(ab);
|
||||||
|
}
|
||||||
|
const src = ctx.createBufferSource();
|
||||||
|
src.buffer = buf;
|
||||||
|
src.connect(ctx.destination);
|
||||||
|
src.start();
|
||||||
|
return new Promise(resolve => { src.onended = resolve; });
|
||||||
|
}
|
||||||
|
|
||||||
async function playRec(id) {
|
async function playRec(id) {
|
||||||
try {
|
try {
|
||||||
const db = await openRecDB();
|
const db = await openRecDB();
|
||||||
const tx = db.transaction('recs', 'readonly');
|
const tx = db.transaction('recs', 'readonly');
|
||||||
const store = tx.objectStore('recs');
|
const store = tx.objectStore('recs');
|
||||||
const rec = await new Promise(r => { const req = store.get(id); req.onsuccess = () => r(req.result); });
|
const rec = await new Promise(r => { const req = store.get(id); req.onsuccess = () => r(req.result); });
|
||||||
if (rec?.blob) {
|
if (rec?.blob) await playTrimmed(rec.blob);
|
||||||
const url = URL.createObjectURL(rec.blob);
|
|
||||||
const audio = new Audio(url);
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
audio.onended = () => { URL.revokeObjectURL(url); resolve(); };
|
|
||||||
audio.onerror = () => { URL.revokeObjectURL(url); reject(); };
|
|
||||||
audio.play();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch(e) { /* тихо */ }
|
} catch(e) { /* тихо */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-11
@@ -1,6 +1,6 @@
|
|||||||
// ---------- Глобальные переменные и инициализация ----------
|
// ---------- Глобальные переменные и инициализация ----------
|
||||||
const GROQ_API_KEY = '';
|
const GROQ_API_KEY = '';
|
||||||
const VERSION = 'v75';
|
const VERSION = 'v76';
|
||||||
|
|
||||||
let mediaRecorder, audioChunks = [], userAudioBlob = null, ttsText = '';
|
let mediaRecorder, audioChunks = [], userAudioBlob = null, ttsText = '';
|
||||||
let isRecording = false, isAnalyzing = false, micAnalyser = null, micAnimId = null;
|
let isRecording = false, isAnalyzing = false, micAnalyser = null, micAnimId = null;
|
||||||
@@ -41,19 +41,12 @@ document.getElementById('btnRecord').onclick = async function () {
|
|||||||
// ---------- Воспроизведение своей записи ----------
|
// ---------- Воспроизведение своей записи ----------
|
||||||
document.getElementById('btnPlayUser').onclick = async () => {
|
document.getElementById('btnPlayUser').onclick = async () => {
|
||||||
if (!userAudioBlob) return setStatus('Нет записи!');
|
if (!userAudioBlob) return setStatus('Нет записи!');
|
||||||
if (userAudioBlob.size === 0) return setStatus('Запись пустая!');
|
|
||||||
const url = URL.createObjectURL(userAudioBlob);
|
|
||||||
const audio = new Audio(url);
|
|
||||||
audio.volume = 1.0;
|
|
||||||
audio.onended = () => { URL.revokeObjectURL(url); setStatus('Готово.'); };
|
|
||||||
audio.onerror = () => { URL.revokeObjectURL(url); setStatus('Ошибка воспроизведения.'); };
|
|
||||||
setStatus('🔊 Ваша запись...');
|
setStatus('🔊 Ваша запись...');
|
||||||
try {
|
try {
|
||||||
await audio.play();
|
await playTrimmed(userAudioBlob);
|
||||||
|
setStatus('Готово.');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
URL.revokeObjectURL(url);
|
setStatus('Ошибка воспроизведения.');
|
||||||
setStatus('Ошибка воспроизведения — нажмите ещё раз.');
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user