Skip to content

fix: acquire getUserMedia early in start() to prevent mobile gesture timeout#156

Closed
vapi-tasker[bot] wants to merge 1 commit intomainfrom
tasker/VAP-12773-fix-getusermedia-mobile-gesture
Closed

fix: acquire getUserMedia early in start() to prevent mobile gesture timeout#156
vapi-tasker[bot] wants to merge 1 commit intomainfrom
tasker/VAP-12773-fix-getusermedia-mobile-gesture

Conversation

@vapi-tasker
Copy link

@vapi-tasker vapi-tasker bot commented Mar 18, 2026

Summary\n\nFixes the NotAllowedError on mobile browsers (iOS Safari, Chrome on Android) when calling vapi.start(). Mobile browsers enforce a strict "user gesture" policy that only allows getUserMedia() within ~1-5 seconds of a user tap. Previously, the SDK made a network API call to create the web call before invoking getUserMedia() (via DailyIframe.createCallObject), causing the gesture window to expire.\n\nRoot cause: In the start() method, callControllerCreateWebCall (network request) ran first, and DailyIframe.createCallObject({ audioSource: true }) ran second. On slow networks or under load, the API call consumed the gesture window, and Daily's internal getUserMedia() call was rejected by the browser.\n\nFix: Call navigator.mediaDevices.getUserMedia({ audio: true }) immediately when start() is invoked (within the gesture window), then pass the pre-acquired MediaStreamTrack to DailyIframe.createCallObject({ audioSource: track }) so Daily does not call getUserMedia() again.\n\n### Changes\n\n- Early media acquisition: getUserMedia() is called at the top of start(), before any network requests\n- Pre-acquired track passthrough: The track is passed as audioSource to DailyIframe.createCallObject()\n- Graceful fallback: If early getUserMedia() fails, the SDK falls back to the original behavior (Daily handles it)\n- Track cleanup on error: Pre-acquired tracks are stopped if the call creation fails, freeing the microphone\n- User-provided MediaStream support: New mediaStream option in StartCallOptions allows callers to pass their own pre-acquired MediaStream\n- Constructor track detection: If audioSource in constructor options is already a MediaStreamTrack, getUserMedia() is skipped\n\n## Test Plan\n\n- [x] 7 new tests in __tests__/vapi-early-media.test.ts covering:\n - getUserMedia is called before the API call (ordering verified via call tracking)\n - Pre-acquired audio track is passed to DailyIframe.createCallObject\n - Fallback works when getUserMedia fails (Permission denied)\n - Only audio is requested (not video)\n - Pre-acquired tracks are stopped on cleanup when call creation fails\n - User-provided MediaStream is accepted and used (skipping internal getUserMedia)\n - Constructor-provided MediaStreamTrack skips getUserMedia\n- [x] All 13 tests pass (6 existing + 7 new)\n- [x] TypeScript build passes (pre-existing type errors unrelated to this change)\n\n## Linear Issue\n\nResolves VAP-12773

…timeout

Mobile browsers enforce strict user gesture policies that only allow a
short window (~1-5s) between a user tap and a getUserMedia() call.
Previously, the SDK made an API call to create the web call BEFORE
calling getUserMedia (via DailyIframe.createCallObject), causing
NotAllowedError on mobile when the API call took too long.

This fix calls getUserMedia() immediately when start() is invoked,
within the user gesture window, before any async network calls. The
pre-acquired audio track is then passed to DailyIframe.createCallObject.

Also adds support for passing a pre-acquired MediaStream via the
options.mediaStream parameter for callers who want even more control.

VAP-12773
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants