Repository navigation
Keep tap sound playback off the main thread and recover from output device changes - #22
Merged
Merged
Conversation
Sharing a screen with audio in Google Meet changes the output device's buffer size under the running AVAudioEngine, which then fails every render cycle (kAudioUnitErr_TooManyFramesToProcess). Playing a tap on the main thread waited on that broken render loop, freezing the pointer for about five seconds per click. - Run every engine call on a dedicated serial queue, never on the main thread. - Build a fresh engine when sound resumes, when the audio configuration changes, and when a scheduled tap is never rendered, instead of reusing one prepared for an old device setup; leave headroom in the frames per render cycle. - Schedule taps without .interrupts, round-robin over a small pool of player nodes so quick taps overlap.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #23.
Problem
With the tap sound on, sharing the screen with system audio in Google Meet freezes the pointer for about 5 seconds per click. Queued clicks are processed one every 5 seconds, which shows in
~/Library/Logs/TinyHand.logas presses spaced exactly 5 s apart, each with its release logged a few ms later.Cause
Meet's system audio capture changes the output device's buffer size under the running
AVAudioEngine. The engine had been prepared at launch for the previous size, so every render cycle fails from that point on. The system log showskAudioUnitErr_TooManyFramesToProcess: inFramesToProcess=960, mMaxFramesPerSlice=941about 50 times a second.TapSound.play()runs on the main thread, and scheduling a buffer with.interruptswaits on a render thread that makes no progress, so the click handler blocks and the pointer stops drawing. NoAVAudioEngineConfigurationChangewas observed, so the engine never recovers.Changes (
Sources/TinyHand/TapSound.swiftonly)AVAudioEnginework runs on a dedicated serial queue.play()only dispatches to it, so a misbehaving audio device can cost a tap sound but can no longer block the pointer.AVAudioEngineis built when sound resumes after the idle stop, onAVAudioEngineConfigurationChange, and when a scheduled buffer is not rendered within its duration plus 1 s. This last case covers device changes that send no notification.maximumFramesToRenderon the player and mixer nodes is raised to 4096 as extra headroom..interrupts, round-robin over a pool of 4 player nodes, so taps that come quickly overlap instead of waiting on the render thread. The sounds are 50 to 140 ms long.Testing
Built locally and tested on macOS 27.2 during a Google Meet screen share with system audio: the pointer answers every click immediately and the tap sound still plays while sharing. Behavior without screen sharing is unchanged.