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

Expo React Native项目中sound.play未定义错误排查求助

问题分析与解决方案

核心问题

你遇到的sound.play is not a function错误主要来自两个关键点:

  1. 选库不匹配:@react-native-community/audio-toolkit仅用于播放本地/远程音频文件,根本没有AudioGenerator这类实时音频生成API。你代码里的AudioGenerator不属于该库,导致生成的newSound不是具备play/stop方法的音频实例。
  2. 逻辑漏洞:组件顶层直接调用sound.play(sound),此时sound初始值是数组[],自然没有play方法;同时重复触发的play/stop操作会引发资源泄漏。

正确解决方案(基于Expo AV)

Expo环境下推荐用expo-av实现实时音频生成与播放,以下是修正后的完整代码:

步骤1:安装依赖

npx expo install expo-av

步骤2:修正代码

import React, { useState, useEffect, useRef } from 'react';
import { View, Text, Slider } from 'react-native';
import { Audio } from 'expo-av';

const FrequencyGenerator = () => {
  const [frequency, setFrequency] = useState(20);
  const [freq, setFreq] = useState(20);
  const soundRef = useRef(null); // 用ref保存音频实例,避免状态更新重复创建

  // 生成正弦波并播放
  const generateAndPlaySound = async (freq) => {
    // 清理旧音频实例
    if (soundRef.current) {
      await soundRef.current.stopAsync();
      await soundRef.current.unloadAsync();
      soundRef.current = null;
    }

    const sampleRate = 44100;
    const duration = 0.5; // 生成0.5秒音频,循环播放实现持续输出
    const numSamples = sampleRate * duration;
    const pcmData = new Float32Array(numSamples);

    // 生成正弦波PCM数据
    for (let i = 0; i < numSamples; i++) {
      const time = i / sampleRate;
      pcmData[i] = Math.sin(2 * Math.PI * freq * time) * 0.5; // 0.5限制音量避免失真
    }

    // 转换为WAV格式base64并加载播放
    const audioBuffer = await Audio.createAudioAsync(
      { uri: `data:audio/wav;base64,${encodePCMToWavBase64(pcmData, sampleRate)}` },
      { shouldPlay: true, isLooping: true }
    );
    soundRef.current = audioBuffer.sound;
  };

  // 将PCM数据编码为WAV格式base64
  const encodePCMToWavBase64 = (pcmData, sampleRate) => {
    const buffer = new ArrayBuffer(44 + pcmData.length * 2);
    const view = new DataView(buffer);

    // 写入WAV文件头
    view.setUint8(0, 0x52); view.setUint8(1, 0x49); view.setUint8(2, 0x46); view.setUint8(3, 0x46);
    view.setUint32(4, 44 + pcmData.length * 2, true);
    view.setUint8(8, 0x57); view.setUint8(9, 0x41); view.setUint8(10, 0x56); view.setUint8(11, 0x45);
    view.setUint8(12, 0x66); view.setUint8(13, 0x6D); view.setUint8(14, 0x74); view.setUint8(15, 0x20);
    view.setUint32(16, 16, true); view.setUint16(20, 1, true); view.setUint16(22, 1, true);
    view.setUint32(24, sampleRate, true); view.setUint32(28, sampleRate * 2, true);
    view.setUint16(32, 2, true); view.setUint16(34, 16, true);
    view.setUint8(36, 0x64); view.setUint8(37, 0x61); view.setUint8(38, 0x74); view.setUint8(39, 0x61);
    view.setUint32(40, pcmData.length * 2, true);

    // 将Float32转换为16位整数
    for (let i = 0; i < pcmData.length; i++) {
      const sample = Math.max(-1, Math.min(1, pcmData[i]));
      const intSample = sample < 0 ? sample * 32768 : sample * 32767;
      view.setInt16(44 + i * 2, intSample, true);
    }

    return btoa(String.fromCharCode(...new Uint8Array(buffer)));
  };

  useEffect(() => {
    // 请求音频权限
    const initAudio = async () => await Audio.requestPermissionsAsync();
    initAudio();

    // 组件卸载时清理音频资源
    return async () => {
      if (soundRef.current) {
        await soundRef.current.stopAsync();
        await soundRef.current.unloadAsync();
      }
    };
  }, []);

  useEffect(() => {
    // 频率变化时重新生成音频
    generateAndPlaySound(frequency);
  }, [frequency]);

  const onSliderValueChange = (value) => {
    const newFreq = Math.ceil(value);
    setFrequency(newFreq);
    setFreq(newFreq);
  };

  return (
    <View style={{ flex: 1, justifyContent: 'center', padding: 20 }}>
      <Slider
        minimumTrackTintColor="pink"
        minimumValue={20}
        maximumValue={20000}
        onValueChange={onSliderValueChange}
        value={frequency}
      />
      <Text style={{ textAlign: 'center', marginTop: 20, fontSize: 20 }}>{freq} HZ</Text>
    </View>
  );
};

export default FrequencyGenerator;

关键说明

  • 库替换:用expo-av替代原库,它原生支持Expo环境,且能处理自定义音频数据。
  • 资源管理:用useRef保存音频实例,避免状态更新重复创建;频率变化或组件卸载时,及时清理旧实例防止泄漏。
  • 循环播放:通过设置isLooping: true实现持续音频输出,无需重复生成长音频。
  • 权限处理:初始化时请求音频权限,确保应用正常播放音频。

内容的提问来源于stack exchange,提问作者LJ-dev

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 14:07:08