Expo React Native WebView中getUserMedia Promise无响应问题求助
问题:Expo React Native WebView中
getUserMedia无响应(麦克风权限) 我用Expo构建了一个React Native应用,它仅作为响应式网站的WebView容器。网页端调用麦克风录音功能正常,但在App端出现异常:即使授权了权限,navigator.mediaDevices.getUserMedia的Promise既不resolve也不reject,日志只打印出"trying to get permissions.",后续无任何成功或失败的输出。
React Native应用代码
import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context"; import { StyleSheet } from "react-native"; import { WebView } from "react-native-webview"; export default function App() { const debugging = ` const consoleLog = (type, log) => window.ReactNativeWebView.postMessage(JSON.stringify({'type': 'Console', 'data': {'type': type, 'log': log}})); console = { log: (log) => consoleLog('log', log), debug: (log) => consoleLog('debug', log), info: (log) => consoleLog('info', log), warn: (log) => consoleLog('warn', log), error: (log) => consoleLog('error', log), }; `; const onMessage = (payload: any) => { let dataPayload; try { dataPayload = JSON.parse(payload.nativeEvent.data); } catch (e: unknown) { let message = "Parsing failed: "; if (e instanceof Error) message += e.message; console.log(message); } if (dataPayload) { if (dataPayload.type === "Console") { console.info(`[Console] ${JSON.stringify(dataPayload.data)}`); } else { console.log(dataPayload); } } else { console.log("No payload"); } }; return ( <SafeAreaProvider> <SafeAreaView style={styles.container}> <WebView source={{ uri: "https://web.mynders.com" }} mediaCapturePermissionRequestType="grant" injectedJavaScript={debugging} onMessage={onMessage} style={styles.webView} /> </SafeAreaView> </SafeAreaProvider> ); } const styles = StyleSheet.create({ container: { flex: 1, borderWidth: 0, }, webView: { flex: 1, borderWidth: 0, }, });
Web应用权限请求代码
import React, { useState } from "react"; import { PiMicrophoneSlashLight } from "react-icons/pi"; const RecordButton: React.FC = () => { const [permission, setPermission] = useState(false); const getMicrophonePermission = async () => { if ("MediaRecorder" in window) { try { console.log("trying to get permissions."); const stream = await navigator.mediaDevices.getUserMedia({ audio: true, }); console.log("received permissions."); setPermission(true); console.log("Prmission was set to true."); stream.getTracks().forEach((t) => t.stop()); console.log("cleared all tracks."); } catch (err: unknown) { let message = "Failed to get mic permissions: "; if (err instanceof Error) message += err.message; console.log(message); alert(message); } } else { console.log("The MediaRecorder API is not supported in your browser."); alert("The MediaRecorder API is not supported in your browser."); } }; if (!permission) return ( <div onClick={getMicrophonePermission} className={[ "rounded-full cursor-pointer m-1 transition-all ease-in-out bg-gray-200 p-2", ].join(" ")} > <PiMicrophoneSlashLight className="h-5 w-5 text-gray-400 select-none cursor-pointer" /> </div> ); }; export default RecordButton;
日志输出
INFO [Console] {"type":"log","log":"trying to get."}
app.json权限配置(Android)
{ "expo": { ... "android": { ... "permissions": [ "android.permission.INTERNET", "android.permission.READ_EXTERNAL_STORAGE", "android.permission.WRITE_EXTERNAL_STORAGE", "android.permission.MODIFY_AUDIO_SETTINGS", "android.permission.AUDIO_CAPTURE", "android.permission.CAMERA", "android.permission.RECORD_AUDIO", "android.permission.ACCESS_FINE_LOCATION", "android.permission.ACCESS_COARSE_LOCATION", "android.permission.FOREGROUND_SERVICE" ] }, } }
解决方案
1. 先通过Expo API获取原生权限
WebView的mediaCapturePermissionRequestType="grant"无法自动处理原生权限流程,必须先通过Expo的权限API主动获取录音权限,再渲染WebView:
import { useEffect, useState } from 'react'; import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context"; import { StyleSheet, Alert } from "react-native"; import { WebView } from "react-native-webview"; import * as Permissions from 'expo-permissions'; export default function App() { const [hasAudioPermission, setHasAudioPermission] = useState(false); const [loading, setLoading] = useState(true); useEffect(() => { const requestPermissions = async () => { const { status } = await Permissions.askAsync(Permissions.AUDIO_RECORDING); if (status === 'granted') { setHasAudioPermission(true); } else { Alert.alert('权限不足', '需要录音权限才能使用该功能'); } setLoading(false); }; requestPermissions(); }, []); // 保留原有debugging和onMessage逻辑... if (loading) { return <SafeAreaView style={styles.container} />; // 加载占位 } return ( <SafeAreaProvider> <SafeAreaView style={styles.container}> {hasAudioPermission && ( <WebView source={{ uri: "https://web.mynders.com" }} mediaCapturePermissionRequestType="grant" injectedJavaScript={debugging} onMessage={onMessage} style={styles.webView} allowFileAccess={true} allowMediaPlayback={true} /> )} </SafeAreaView> </SafeAreaProvider> ); } // 保留原有styles...
2. 精简并修正权限配置
移除冗余权限,确保核心权限正确,同时补充iOS的权限描述(必填):
{ "expo": { ... "android": { ... "permissions": [ "android.permission.INTERNET", "android.permission.RECORD_AUDIO", "android.permission.MODIFY_AUDIO_SETTINGS" ] }, "ios": { ... "infoPlist": { "NSMicrophoneUsageDescription": "需要访问麦克风进行音频录制" } } } }
3. 添加WebView权限回调监听
手动处理WebView的权限请求事件,确保权限状态同步:
<WebView ... onMediaCapturePermissionRequest={(event) => { if (hasAudioPermission) { event.url && event.grant(true); } else { event.url && event.grant(false); Alert.alert('需要先授予录音权限'); } }} />
4. 测试真机环境
Expo Go的沙箱环境可能限制媒体权限,建议构建自定义开发客户端或直接打包APK/IPA测试,避免环境限制导致的异常。
内容的提问来源于stack exchange,提问作者Tsabary
相关产品推荐
相关产品推荐

