Skip to content

Zg/ts quickstart audio context #170

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 30, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion evi/evi-next-js-app-router-quickstart/package.json
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@
"lint": "next lint"
},
"dependencies": {
"@humeai/voice-react": "^0.1.20",
"@humeai/voice-react": "^0.1.22",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-toggle": "^1.0.3",
4,578 changes: 2,638 additions & 1,940 deletions evi/evi-next-js-app-router-quickstart/pnpm-lock.yaml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion evi/evi-next-js-pages-router-quickstart/package.json
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@
"lint": "next lint"
},
"dependencies": {
"@humeai/voice-react": "^0.1.20",
"@humeai/voice-react": "^0.1.22",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-toggle": "^1.0.3",
32 changes: 20 additions & 12 deletions evi/evi-next-js-pages-router-quickstart/pnpm-lock.yaml
2 changes: 1 addition & 1 deletion evi/evi-typescript-quickstart/package.json
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@
"preview": "vite preview"
},
"dependencies": {
"hume": "^0.10.0"
"hume": "^0.10.3"
},
"devDependencies": {
"typescript": "^5.2.2",
10 changes: 5 additions & 5 deletions evi/evi-typescript-quickstart/pnpm-lock.yaml
62 changes: 40 additions & 22 deletions evi/evi-typescript-quickstart/src/main.ts
Original file line number Diff line number Diff line change
@@ -43,7 +43,8 @@ const ABNORMAL_CLOSE_CODES = new Set([1006, 1011, 1012, 1013, 1014]); // WebSock

/**--- Audio Playback State ---*/
const audioQueue: Blob[] = [];
let currentAudio: HTMLAudioElement | null = null;
const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();
let currentSource: AudioBufferSourceNode | null = null;
let isPlaying = false;

/**--- Reconnection State ---*/
@@ -228,40 +229,57 @@ const ABNORMAL_CLOSE_CODES = new Set([1006, 1011, 1012, 1013, 1014]); // WebSock

/**--- Audio Playback Functions ---*/
/** Plays the next audio chunk from the queue if available and not already playing. */
function playNextAudioChunk(): void {
// Don't play if already playing or queue is empty
async function playNextAudioChunk(): Promise<void> {
if (isPlaying || audioQueue.length === 0) return;

isPlaying = true;
const audioBlob = audioQueue.shift();

const audioBlob = audioQueue.shift();
if (!audioBlob) {
isPlaying = false;
return;
}
const audioUrl = URL.createObjectURL(audioBlob);
currentAudio = new Audio(audioUrl);
currentAudio.play();
currentAudio.onended = () => {
URL.revokeObjectURL(audioUrl);
currentAudio = null;

try {
// Safari requires a user gesture–driven resume
if (audioContext.state === 'suspended') {
await audioContext.resume();
}
// Decode the blob into an AudioBuffer
const arrayBuffer = await audioBlob.arrayBuffer();
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);

// Create a source node and play it
const source = audioContext.createBufferSource();
source.buffer = audioBuffer;
source.connect(audioContext.destination);
source.start();

// Track it so we can stop mid-stream if needed
currentSource = source;
source.onended = () => {
currentSource = null;
isPlaying = false;
playNextAudioChunk();
};
} catch (error) {
console.error("Error during audio playback:", error);
isPlaying = false;
playNextAudioChunk(); // Recursively play the next chunk if queue is not empty
};
}
}

/** Stops the currently playing audio and clears the playback queue. */
function stopAudioPlayback(): void {
if (currentAudio) {
currentAudio.pause();
console.log("Audio playback paused.");
if (currentAudio.src && currentAudio.src.startsWith('blob:')) {
URL.revokeObjectURL(currentAudio.src); // Revoke URL if paused mid-play
}
currentAudio = null;
// Stop any in-flight buffer source
if (currentSource) {
try {
currentSource.stop();
} catch {}
currentSource.disconnect();
currentSource = null;
}
audioQueue.length = 0; // Clear the queue
isPlaying = false; // Reset playback state
// Clear the queue and reset state
audioQueue.length = 0;
isPlaying = false;
}

/**--- UI and Helper Functions ---*/