Skip to content

Commit ea4cd01

Browse files
Add audio output device selector with setSinkId routing to headphones
1 parent 1bcb06a commit ea4cd01

1 file changed

Lines changed: 59 additions & 6 deletions

File tree

ask.html

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,28 @@
341341
font-family: 'Fira Code', monospace;
342342
}
343343

344+
/* ── Audio output selector ── */
345+
#audio-output-selector {
346+
display: flex;
347+
align-items: center;
348+
gap: 0.4rem;
349+
margin-top: 0.5rem;
350+
font-size: 0.7rem;
351+
color: var(--text-dim);
352+
font-family: 'Fira Code', monospace;
353+
}
354+
#audio-output-selector select {
355+
background: var(--panel);
356+
color: var(--text);
357+
border: 1px solid var(--border);
358+
border-radius: 4px;
359+
padding: 0.15rem 0.3rem;
360+
font-size: 0.7rem;
361+
font-family: 'Fira Code', monospace;
362+
max-width: 140px;
363+
cursor: pointer;
364+
}
365+
344366
/* ── Orb tap ripple ── */
345367
.tap-ripple {
346368
position: absolute;
@@ -413,6 +435,10 @@
413435
<button class="voice-dot" data-voice="3" title="Atlas — deep and confident" style="--dot-color:#9b59b6"></button>
414436
<button class="voice-dot" data-voice="4" title="Iris — bright and cheerful" style="--dot-color:#e74c3c"></button>
415437
</div>
438+
<div id="audio-output-selector">
439+
<span id="audio-device-label">Audio: </span>
440+
<select id="audio-device-select"></select>
441+
</div>
416442
<div id="interrupt-hint">Speak to interrupt</div>
417443

418444
<div id="live-transcript"></div>
@@ -457,6 +483,7 @@
457483
let currentSpeakResolve = null;
458484
let speakChain = Promise.resolve();
459485
let streamAbortController = null;
486+
let audioOutputDevices = [];
460487
let audioLevel = 0;
461488
let targetAudioLevel = 0;
462489

@@ -993,12 +1020,33 @@
9931020
// Auto-request mic and enumerate audio devices (triggers permission prompts)
9941021
if (navigator.mediaDevices) {
9951022
navigator.mediaDevices.getUserMedia({ audio: true }).catch(() => {});
996-
navigator.mediaDevices.enumerateDevices().then(devices => {
997-
const audioOuts = devices.filter(d => d.kind === 'audiooutput' && d.deviceId);
998-
if (liveAudioEl && audioOuts.length > 0 && liveAudioEl.setSinkId) {
999-
liveAudioEl.setSinkId(audioOuts[0].deviceId).catch(() => {});
1000-
}
1001-
}).catch(() => {});
1023+
const setupAudioOutput = () => {
1024+
navigator.mediaDevices.enumerateDevices().then(devices => {
1025+
audioOutputDevices = devices.filter(d => d.kind === 'audiooutput');
1026+
const sel = document.getElementById('audio-device-select');
1027+
if (sel) {
1028+
const currentId = liveAudioEl?.sinkId || '';
1029+
sel.innerHTML = audioOutputDevices.map(d =>
1030+
`<option value="${d.deviceId}" ${d.deviceId === currentId ? 'selected' : ''}>${d.label || d.deviceId.slice(0, 8)}</option>`
1031+
).join('');
1032+
}
1033+
// Try to select a non-default audio output (headphones)
1034+
if (liveAudioEl && liveAudioEl.setSinkId) {
1035+
const preferred = audioOutputDevices.find(d => d.deviceId !== 'default' && d.deviceId);
1036+
if (preferred && preferred.deviceId !== liveAudioEl.sinkId) {
1037+
liveAudioEl.setSinkId(preferred.deviceId).catch(() => {});
1038+
}
1039+
}
1040+
}).catch(() => {});
1041+
};
1042+
// Request speaker-selection permission if API exists
1043+
if (navigator.permissions && navigator.permissions.request) {
1044+
navigator.permissions.request({ name: 'speaker-selection' }).then(() => {
1045+
setupAudioOutput();
1046+
}).catch(() => setupAudioOutput());
1047+
} else {
1048+
setupAudioOutput();
1049+
}
10021050
}
10031051
playTestTone();
10041052
setVoice(currentVoice);
@@ -1210,6 +1258,11 @@
12101258
document.querySelectorAll('.voice-dot').forEach(dot => {
12111259
dot.addEventListener('click', () => setVoice(parseInt(dot.dataset.voice)));
12121260
});
1261+
document.getElementById('audio-device-select').addEventListener('change', e => {
1262+
if (liveAudioEl && liveAudioEl.setSinkId) {
1263+
liveAudioEl.setSinkId(e.target.value).catch(() => {});
1264+
}
1265+
});
12131266

12141267
// Orb tap to start/stop listening
12151268
canvas.addEventListener('click', e => {

0 commit comments

Comments
 (0)