You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在React-Native Expo项目中集成Deepgram?解决401授权错误

React Native Expo 集成 Deepgram 实时音频转录解决方案

错误原因分析

你遇到的401 Unauthorized错误是API密钥无效或未正确配置导致的,另外代码中实时转录逻辑存在多处问题:连接时机错误(停止录音才创建连接)、未实时发送音频数据、音频格式不匹配Deepgram要求等。

分步解决

1. 正确配置Deepgram API密钥

  • 禁止硬编码密钥,改用Expo环境变量:
    1. 在app.json中添加配置:
      {
        "expo": {
          "extra": {
            "DEEPGRAM_API_KEY": "你的真实Deepgram密钥"
          }
        }
      }
      
    2. 安装expo-constants用于读取环境变量:
      npx expo install expo-constants
      
    3. 在代码中导入并使用:
      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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 01:44:55