Summary
session.speaker.isPlaying returns false for the entire duration of playback started with
speaker.createStream().
_state is only ever assigned from an inbound SPEAKER_STATE envelope (modules/speaker.ts,
_applyState). createStream() sets nothing locally, so unless the host emits a state event for
streamed PCM, isPlaying reads idle while audio is actively playing.
Impact
isPlaying is the natural guard for three things in a voice app, and all three broke at once for us
without any error:
- Barge-in.
if (session.speaker.isPlaying) { session.speaker.stop() } never fired, so a button
press fell through to opening the microphone instead of stopping the answer.
- Echo suppression. "Do not capture while speaking" let a follow-up capture open on top of our
own playback.
- Ambient level measurement, which trained on our own voice.
The tell was that a code path calling speaker.stop() unconditionally worked instantly, while the
one guarded by isPlaying never triggered.
Reproduction
const stream = await session.speaker.createStream({ sampleRate: 24000 })
const write = stream.write(pcm)
console.log(session.speaker.isPlaying) // false, while audio is audibly playing
await write
await stream.close()
close() is documented as "Resolves when playback has ended", and it does — so the SDK clearly knows
when playback is running. It just is not reflected in state/isPlaying.
Suggested fix
Set _state = "playing" locally when createStream() succeeds and clear it on close()/abort(),
or emit SPEAKER_STATE host-side for streamed playback the way play() and speak() presumably do.
Either way it would help to document which of play(), speak() and createStream() drive
isPlaying, since today they appear to differ and the difference is invisible.
Related
speaker.stop() does stop a createStream() playback — only the state reporting is missing. Note
that stop() is a one-shot with no result, so an app that tracks playback itself has no completion
signal after stopping; we clear our own flag at the call site.
Environment
@mentra/miniapp 3.1.0-dev.27
- Device: Mentra Live, MentraOS app
staging.20260808.*
- Playback: 24 kHz PCM via
createStream()
Summary
session.speaker.isPlayingreturnsfalsefor the entire duration of playback started withspeaker.createStream()._stateis only ever assigned from an inboundSPEAKER_STATEenvelope (modules/speaker.ts,_applyState).createStream()sets nothing locally, so unless the host emits a state event forstreamed PCM,
isPlayingreadsidlewhile audio is actively playing.Impact
isPlayingis the natural guard for three things in a voice app, and all three broke at once for uswithout any error:
if (session.speaker.isPlaying) { session.speaker.stop() }never fired, so a buttonpress fell through to opening the microphone instead of stopping the answer.
own playback.
The tell was that a code path calling
speaker.stop()unconditionally worked instantly, while theone guarded by
isPlayingnever triggered.Reproduction
close()is documented as "Resolves when playback has ended", and it does — so the SDK clearly knowswhen playback is running. It just is not reflected in
state/isPlaying.Suggested fix
Set
_state = "playing"locally whencreateStream()succeeds and clear it onclose()/abort(),or emit
SPEAKER_STATEhost-side for streamed playback the wayplay()andspeak()presumably do.Either way it would help to document which of
play(),speak()andcreateStream()driveisPlaying, since today they appear to differ and the difference is invisible.Related
speaker.stop()does stop acreateStream()playback — only the state reporting is missing. Notethat
stop()is a one-shot with no result, so an app that tracks playback itself has no completionsignal after stopping; we clear our own flag at the call site.
Environment
@mentra/miniapp3.1.0-dev.27staging.20260808.*createStream()