如何在React-Native Expo项目中集成Deepgram?解决401授权错误
React Native Expo 集成 Deepgram 实时音频转录解决方案
错误原因分析
你遇到的401 Unauthorized错误是API密钥无效或未正确配置导致的,另外代码中实时转录逻辑存在多处问题:连接时机错误(停止录音才创建连接)、未实时发送音频数据、音频格式不匹配Deepgram要求等。
分步解决
1. 正确配置Deepgram API密钥
- 禁止硬编码密钥,改用Expo环境变量:
- 在
app.json中添加配置:{ "expo": { "extra": { "DEEPGRAM_API_KEY": "你的真实Deepgram密钥" } } } - 安装
expo-constants用于读取环境变量:npx expo install expo-constants - 在代码中导入并使用:
import Constants from 'expo-constants'; const deepgram = createClient(Constants.expoConfig?.extra?.DEEPGRAM_API_KEY || '');
- 在
- 确认你的Deepgram密钥拥有实时转录权限,可在Deepgram控制台的项目密钥页面验证。
2. 修正实时转录逻辑(边录边传)
实时转录需要在开始录音时建立Deepgram连接,并将录制的音频数据实时发送给API:
- 使用Expo Audio的
addAudioEventListener监听录音的原始数据 - 配置录制格式为Deepgram要求的:16kHz采样率、单声道、16-bit PCM编码
3. 修复异步状态问题
setConnection是异步状态更新,不能在调用后立即访问connection,需通过useEffect监听connection变化来绑定事件。
修正后的完整代码
import { Button, NativeEventEmitter, StyleSheet } from "react-native"; import { LiveClient, LiveTranscriptionEvents, createClient, } from "@deepgram/sdk"; import { Text, View } from "@/components/Themed"; import { Audio } from "expo-av"; import { useEffect, useState } from "react"; import Constants from 'expo-constants'; export default function TabOneScreen() { const [recording, setRecording] = useState<Audio.Recording | undefined>(); const [recordings, setRecordings] = useState<Array<{ sound: Audio.Sound; duration: string; file: string | null; }>>([]); const [permissionResponse, requestPermission] = Audio.usePermissions(); const [connection, setConnection] = useState<LiveClient | null>(); const [caption, setCaption] = useState<string | null>(null); // 从环境变量读取API密钥 const deepgram = createClient(Constants.expoConfig?.extra?.DEEPGRAM_API_KEY || ''); useEffect(() => { if (!connection) return; // 绑定Deepgram事件 connection.on(LiveTranscriptionEvents.Open, () => { console.log("Deepgram连接已建立"); }); connection.on(LiveTranscriptionEvents.Close, () => { console.log("Deepgram连接已关闭"); }); connection.on(LiveTranscriptionEvents.Transcript, (data) => { if (data.channel?.alternatives[0]?.transcript) { setCaption(data.channel.alternatives[0].transcript); } }); connection.on(LiveTranscriptionEvents.Error, (error) => { console.error("Deepgram错误:", error); }); // 组件卸载时关闭连接 return () => { connection.finish(); }; }, [connection]); async function startRecording() { try { if (permissionResponse?.status !== "granted") { await requestPermission(); } await Audio.setAudioModeAsync({ allowsRecordingIOS: true, playsInSilentModeIOS: true, recordingDevice: Audio.RECORDING_DEVICE_BUILTIN_MIC, }); // 配置符合Deepgram要求的录制参数 const recordingOptions: Audio.RecordingOptions = { android: { extension: '.wav', sampleRate: 16000, numberOfChannels: 1, bitRate: 128000, }, ios: { extension: '.wav', sampleRate: 16000, numberOfChannels: 1, linearPCMBitDepth: 16, linearPCMIsFloat: false, }, }; const { recording } = await Audio.Recording.createAsync(recordingOptions); setRecording(recording); // 建立Deepgram实时连接 const liveConnection = deepgram.listen.live({ model: "nova", smart_format: true, encoding: "linear16", sample_rate: 16000, channels: 1, }); setConnection(liveConnection); // 实时发送音频数据到Deepgram recording.addAudioEventListener("data", ({ data }) => { if (liveConnection.getReadyState() === 1) { liveConnection.send(data); } }); console.log("录音已开始,实时转录启动"); } catch (err) { console.error("启动录音失败:", err); } } async function stopRecording() { if (!recording) return; try { await recording.stopAndUnloadAsync(); setRecording(undefined); // 结束Deepgram连接 connection?.finish(); // 保存录音文件 const { sound, status } = await recording.createNewLoadedSoundAsync(); setRecordings(prev => [...prev, { sound, duration: getDurationFormatted(status.durationMillis), file: recording.getURI(), }]); console.log("录音已停止"); } catch (err) { console.error("停止录音失败:", err); } } function getDurationFormatted(milliseconds: number) { const minutes = Math.floor(milliseconds / 1000 / 60); const seconds = Math.round((milliseconds / 1000) % 60); return `${minutes}:${seconds.toString().padStart(2, '0')}`; } function getRecordingLines() { return recordings.map((item, index) => ( <View key={index} style={styles.row}> <Text style={styles.fill}> 录音 #{index + 1} | {item.duration} </Text> <Button onPress={() => item.sound.replayAsync()} title="播放" /> </View> )); } function clearRecordings() { setRecordings([]); } return ( <View style={styles.container}> <Text style={styles.title}>实时音频转录</Text> <View style={styles.separator} lightColor="#aae" darkColor="rgba(255,255,255,0.1)" /> {caption && <Text style={styles.caption}>实时转录: {caption}</Text>} <Button title={recording ? "停止录音" : "开始录音"} onPress={recording ? stopRecording : startRecording} /> {getRecordingLines()} {recordings.length > 0 && ( <Button title="清空录音" onPress={clearRecordings} /> )} </View> ); } const styles = StyleSheet.create({ container: { flex: 1, alignItems: "center", justifyContent: "flex-start", paddingTop: 50, }, title: { fontSize: 20, fontWeight: "bold", }, separator: { marginVertical: 30, height: 1, width: "80%", }, row: { flexDirection: "row", alignItems: "center", justifyContent: "center", width: "90%", marginVertical: 10, }, fill: { flex: 1, marginRight: 15, }, caption: { fontSize: 16, marginBottom: 20, padding: 10, backgroundColor: "#f0f0f0", borderRadius: 5, width: "80%", textAlign: "center", }, });
额外配置
- iOS需在
app.json的ios.infoPlist添加麦克风权限描述:"ios": { "infoPlist": { "NSMicrophoneUsageDescription": "需要访问麦克风进行音频录制和实时转录" } } - Android需在
app.json的android.permissions添加录音权限:"android": { "permissions": ["RECORD_AUDIO"] }
内容的提问来源于stack exchange,提问作者Rahin_21
相关产品推荐
相关产品推荐

