Harden voice recording initialization (#698)
Build and Release / Read metadata (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage deb updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage deb updater) (push) Has been cancelled
Build and Release / Prepare & create release (push) Has been cancelled
Build and Release / Publish release (push) Has been cancelled

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2026-03-16 22:36:51 +01:00
1 parent 56da27372c
commit dac0b74145
13 files changed
+242 -69

No files matched your search

+57 -35
View File
@@ -21,59 +21,68 @@ const SOUND_EFFECT_PATHS = [
'/sounds/transcription_done.ogg'
];
function createSoundEffectsInitResult(success, failedPaths = [], errorMessage = null) {
return {
success: success,
failedPaths: failedPaths,
errorMessage: errorMessage
};
}
// Initialize the audio context with low-latency settings.
// Should be called from a user interaction (click, keypress)
// to satisfy browser autoplay policies:
window.initSoundEffects = async function() {
if (soundEffectContext && soundEffectContext.state !== 'closed') {
// Already initialized, just ensure it's running:
if (soundEffectContext.state === 'suspended') {
await soundEffectContext.resume();
}
return;
}
try {
// Create the context with the interactive latency hint for the lowest latency:
soundEffectContext = new (window.AudioContext || window.webkitAudioContext)({
latencyHint: 'interactive'
});
if (soundEffectContext && soundEffectContext.state !== 'closed') {
// Already initialized, just ensure it's running:
if (soundEffectContext.state === 'suspended') {
await soundEffectContext.resume();
}
} else {
// Create the context with the interactive latency hint for the lowest latency:
soundEffectContext = new (window.AudioContext || window.webkitAudioContext)({
latencyHint: 'interactive'
});
// Resume immediately (needed for Safari/macOS):
if (soundEffectContext.state === 'suspended') {
await soundEffectContext.resume();
// Resume immediately (needed for Safari/macOS):
if (soundEffectContext.state === 'suspended') {
await soundEffectContext.resume();
}
// Reset the queue timing:
nextAvailablePlayTime = 0;
//
// Play a very short silent buffer to "warm up" the audio pipeline.
// This helps prevent the first real sound from being cut off:
//
const silentBuffer = soundEffectContext.createBuffer(1, 1, soundEffectContext.sampleRate);
const silentSource = soundEffectContext.createBufferSource();
silentSource.buffer = silentBuffer;
silentSource.connect(soundEffectContext.destination);
silentSource.start(0);
console.log('Sound effects - AudioContext initialized with latency:', soundEffectContext.baseLatency);
}
// Reset the queue timing:
nextAvailablePlayTime = 0;
//
// Play a very short silent buffer to "warm up" the audio pipeline.
// This helps prevent the first real sound from being cut off:
//
const silentBuffer = soundEffectContext.createBuffer(1, 1, soundEffectContext.sampleRate);
const silentSource = soundEffectContext.createBufferSource();
silentSource.buffer = silentBuffer;
silentSource.connect(soundEffectContext.destination);
silentSource.start(0);
console.log('Sound effects - AudioContext initialized with latency:', soundEffectContext.baseLatency);
// Preload all sound effects in parallel:
if (!soundEffectsPreloaded) {
await window.preloadSoundEffects();
return await window.preloadSoundEffects();
}
return createSoundEffectsInitResult(true);
} catch (error) {
console.warn('Failed to initialize sound effects:', error);
return createSoundEffectsInitResult(false, [], error?.message || String(error));
}
};
// Preload all sound effect files into the cache:
window.preloadSoundEffects = async function() {
if (soundEffectsPreloaded) {
return;
return createSoundEffectsInitResult(true);
}
// Ensure that the context exists:
@@ -84,10 +93,15 @@ window.preloadSoundEffects = async function() {
}
console.log('Sound effects - preloading', SOUND_EFFECT_PATHS.length, 'sound files...');
const failedPaths = [];
const preloadPromises = SOUND_EFFECT_PATHS.map(async (soundPath) => {
try {
const response = await fetch(soundPath);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const arrayBuffer = await response.arrayBuffer();
const audioBuffer = await soundEffectContext.decodeAudioData(arrayBuffer);
soundEffectCache.set(soundPath, audioBuffer);
@@ -95,12 +109,20 @@ window.preloadSoundEffects = async function() {
console.log('Sound effects - preloaded:', soundPath, 'duration:', audioBuffer.duration.toFixed(2), 's');
} catch (error) {
console.warn('Sound effects - failed to preload:', soundPath, error);
failedPaths.push(soundPath);
}
});
await Promise.all(preloadPromises);
soundEffectsPreloaded = true;
console.log('Sound effects - all files preloaded');
soundEffectsPreloaded = failedPaths.length === 0;
if (soundEffectsPreloaded) {
console.log('Sound effects - all files preloaded');
return createSoundEffectsInitResult(true);
}
console.warn('Sound effects - preload finished with failures:', failedPaths);
return createSoundEffectsInitResult(false, failedPaths, 'One or more sound effects could not be loaded.');
};
window.playSound = async function(soundPath) {