Request microphone permission before audio capture starts - #1183
Open
hiroshihorie wants to merge 4 commits into
Open
Request microphone permission before audio capture starts#1183hiroshihorie wants to merge 4 commits into
hiroshihorie wants to merge 4 commits into
Conversation
webrtc-sdk/webrtc#265 (m144.7559.12 and later) removed the blocking mic permission request from the AudioEngine device. It now only checks the status and fails with kAudioEngineErrorInsufficientDevicePermission, so requesting permission is the SDK's job. The native plugin gains ensureMicrophoneAccess: authorized passes, denied or restricted fails, and notDetermined requests access, on iOS only while the app is active. An inactive or backgrounded app has the alert deferred by the system, and waiting on it would suspend getUserMedia and the publish queue behind it, so it fails fast instead and the next foreground attempt prompts normally. LocalTrack.createStream calls it for audio on Apple platforms before getUserMedia, which covers publishing, restartTrack on unmute, and pre-connect audio. Failures surface as TrackCreateException through the existing audio engine error mapping.
With input availability disabled (the CallKit flow from setEngineAvailability) the audio device module defers opening input entirely and runs no permission check, so gating track creation there turned a working background connect into a deviceAccessDenied failure. The plugin already tracks the last-set availability for both the channel and native static paths, so the handler reads that instead of a module getter. Also restores the setEngineAvailability doc comment that the previous commit accidentally split from its declaration, adds the note that permission must be granted before input availability is restored, and aligns the withPreConnectAudio guidance with client-sdk-swift: request permission up front when calling it before the app becomes active.
hiroshihorie
force-pushed
the
hiroshi/mic-permission-before-capture
branch
from
September 1, 2026 06:21
c6e95ca to
0a0e30c
Compare
hiroshihorie
marked this pull request as ready for review
September 1, 2026 06:25
hiroshihorie
requested review from
cloudwebrtc and
xianshijing-lk
as code owners
September 1, 2026 06:25
startRecording set isRecording and armed the agent-ready timeout before creating the track, so a creation failure (now a designed outcome when the mic permission gate rejects) left the buffer latched: retries were silently ignored while capturing nothing, and the stale timeout later completed the unobserved agentReadyFuture as an unhandled error. Route the failure through the same stopRecording cleanup the later start steps already use. ReusableCompleter.completeError also delivered errors nobody can observe, since future() hands out a fresh completer once completed. Complete silently in that case, matching what reset() and dispose() already do.
…t skip cleanup All failure paths invoked the app-provided onError callback before their own cleanup, so a callback that throws left the buffer latched as recording and replaced the original failure with the callback's own error. Route every call site through a helper that logs and swallows callback errors.
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.
Flutter counterpart of client-sdk-swift #1085, matching its final merged behavior. Builds on #1182.
Why
webrtc-sdk/webrtc#265 (first shipped in
m144.7559.12) removed the blocking mic permission request from the AudioEngine device. The pre-enable check is now passive: it returnskAudioEngineErrorInsufficientDevicePermission(-9000) instead of prompting, so requesting permission is the SDK's job.flutter-webrtc still pins
144.7559.10, so this is not load-bearing yet. It is harmless there, sincegetUserMediain flutter-webrtc already prompts and the status is resolved before the device's blocking path runs. Once flutter-webrtc bumps past.12and the pin here follows, this is what keeps the current behavior.What Flutter already had
flutter-webrtc's
getUserMediacallsAVCaptureDevice requestAccessForMediaType:and waits for the answer, so every livekit_client mic path (publish,restartTrackon unmute, pre-connect audio) already prompted before the audio device saw the track. That part of #1085 needs no port. #1182 already maps -9000 toTrackCreateExceptionfor the direct ADM entry points (setEngineAvailability,startLocalRecording).What this adds
The one behavior from #1085 that was missing: only prompt while the app can show the alert.
ensureMicrophoneAccessinLiveKitPlugin.swift:authorizedpasses,denied/restrictedfail,notDeterminedrequests access. On iOS the request is only made whileUIApplication.shared.applicationState == .active. An inactive or backgrounded app (locked screen, CallKit wake, app switcher) has the alert deferred by the system, and awaiting it would suspendgetUserMediaand the_publishRunnerbehind it, blocking camera and screen share publishes for as long as the app stays there. Failing fast lets the next foreground attempt prompt normally. macOS can present the prompt regardless, so it always requests. No app extension concern here, the plugin is app-only.setEngineAvailability, the CallKit flow), mirroring the same late fix in Add RPC tester interface to example app #1085: the audio device module defers opening input entirely and runs no permission check there, so gating would turn a working background connect into adeviceAccessDeniedfailure. The check reads the plugin's tracked availability value, so it also covers gating done natively before the Flutter engine exists.LocalTrack.createStreamcalls it forAudioCaptureOptionson Apple platforms beforegetUserMedia. That is the Flutter choke point:LocalAudioTrack.create(),restartTrack()andPreConnectAudioBuffer.startRecording()all reach it. Since the prompt in Flutter happens atgetUserMediarather than at capture start, the gate sits in front of that instead of instartCaptureas in Swift.TrackCreateExceptionthrough thedeviceAccessDeniedcode introduced in Resolve a default audio session from engine state when no policy was pushed #1182.withPreConnectAudioandPreConnectAudioBuffer.startRecordingnow say permission is requested at recording start but only while the app is active, so callers running at app launch should request it up front (matching the final Add RPC tester interface to example app #1085 wording).Native.setEngineAvailabilitydocuments that permission is not requested there and must be granted before input availability is restored.Testing
flutter analyze,flutter test,dart format --set-exit-if-changed,import_sorter --exit-if-changedclean.Native.ensureMicrophoneAccess(no-op when unimplemented, propagatesdeviceAccessDenied). ThecreateStreamgate is behindlkPlatformIsApple()and not reachable from unit tests.main. On-device run against a fresh install (first-launch prompt) and a CallKit background wake still to do.Refs CLT-3243, client-sdk-swift#1085