Skip to content

Instantly share code, notes, and snippets.

@steveseguin
Created March 29, 2025 16:45
Show Gist options
  • Save steveseguin/ffdb57b7435fcec797fc89e9299294ba to your computer and use it in GitHub Desktop.
Save steveseguin/ffdb57b7435fcec797fc89e9299294ba to your computer and use it in GitHub Desktop.
Browsers are annoying these days. But solutions exist
function createReliableTimer(callback, delay, data) {
// Check if we're in Electron or OBS Studio
if (window.electronApi || window.obsstudio) {
// Use standard setTimeout for these environments
const timerId = setTimeout(callback, delay, data);
return {
clear: function() {
clearTimeout(timerId);
}
};
}
// For browser environments, try to use AudioContext
let fallbackTimerId = null;
// Try to get an audioContext - might be suspended initially
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
// Create a resume function we can call if needed
const tryResumeAudio = async function() {
if (audioContext.state === "suspended") {
try {
await audioContext.resume();
return audioContext.state === "running";
} catch (e) {
console.warn("Could not resume AudioContext", e);
return false;
}
}
return audioContext.state === "running";
};
// Create a wrapper function to pass the data
const wrappedCallback = function() {
callback(data);
};
// Set up a fallback setTimeout as backup
fallbackTimerId = setTimeout(wrappedCallback, delay);
// Try to set up audio timer if possible
let audioComponents = null;
tryResumeAudio().then(audioRunning => {
if (audioRunning) {
const startTime = audioContext.currentTime;
const oscillator = audioContext.createOscillator();
oscillator.frequency.value = 0; // Silent
const processor = audioContext.createScriptProcessor(4096, 1, 1);
processor.onaudioprocess = function() {
const elapsedTime = (audioContext.currentTime - startTime) * 1000;
if (elapsedTime >= delay) {
// Clear the fallback since we're handling it with audio
if (fallbackTimerId) {
clearTimeout(fallbackTimerId);
fallbackTimerId = null;
}
oscillator.stop();
processor.disconnect();
oscillator.disconnect();
wrappedCallback();
}
};
oscillator.connect(processor);
processor.connect(audioContext.destination);
oscillator.start();
audioComponents = {
oscillator,
processor
};
}
});
return {
clear: function() {
// Clear the fallback timer
if (fallbackTimerId) {
clearTimeout(fallbackTimerId);
fallbackTimerId = null;
}
// Clean up audio components if they were created
if (audioComponents) {
audioComponents.oscillator.stop();
audioComponents.processor.disconnect();
audioComponents.oscillator.disconnect();
}
}
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment