react-wavesurfer录音组件2:前端如何处理后端返回的仅Blob字段

如果后端只返回 Blob 字段,前端需要做以下调整:

1. 调整数据结构

typescript 复制代码
interface AudioData {
  blob: Blob;           // 必需的 Blob 对象
  url?: string;         // 可选的 URL(前端生成)
  wavesurfer?: any;     // WaveSurfer 实例
}

// 或者
interface AudioDataFromBackend {
  blob: Blob;           // 后端返回的二进制音频数据
  // 其他元数据
  mimeType?: string;
  duration?: number;
  fileName?: string;
}

2. 前端处理逻辑

方案A:在父组件中转换

typescript 复制代码
// 父组件中处理
const ParentComponent: React.FC = () => {
  const [audioValue, setAudioValue] = useState<AudioData | null>(null);
  
  // 从后端获取 Blob
  const fetchAudio = async () => {
    try {
      // 假设 API 返回 Blob
      const response = await services.EasyFindController.getAudioBlob();
      
      // 从响应中提取 Blob
      const blob = response.data; // 或者 response.blob
      
      // 生成 URL
      const url = URL.createObjectURL(blob);
      
      // 构建 AudioData
      const audioData: AudioData = {
        blob,
        url,
        wavesurfer: null
      };
      
      setAudioValue(audioData);
    } catch (error) {
      console.error('获取音频失败', error);
    }
  };
  
  return (
    <AudioRecorder
      value={audioValue}
      onChange={handleChange}
    />
  );
};

方案B:修改 AudioRecorder 组件,使其能直接处理 Blob

typescript 复制代码
// AudioRecorder 组件中增加 Blob 处理逻辑
interface AudioRecorderProps {
  value?: AudioData | Blob | null; // 支持 Blob 类型
  // ... 其他 props
}

const AudioRecorder: React.FC<AudioRecorderProps> = ({
  value,
  // ... 其他 props
}) => {
  const [audio, setAudio] = useState<AudioData | null>(null);
  
  // 处理不同类型的 value
  useEffect(() => {
    if (value === null || value === undefined) {
      setAudio(null);
      return;
    }
    
    if (value instanceof Blob) {
      // 处理纯 Blob
      const url = URL.createObjectURL(value);
      const audioData: AudioData = {
        blob: value,
        url,
        wavesurfer: null
      };
      setAudio(audioData);
    } else if ('blob' in value && value.blob) {
      // 已经是 AudioData 类型
      setAudio(value as AudioData);
    }
  }, [value]);
  
  // ... 其他代码
};

3. API 响应示例

方案A:直接返回 Blob(Content-Type: audio/wav)

typescript 复制代码
// 后端 API
// GET /api/audio/record-123
// Response Headers: Content-Type: audio/wav

// 前端调用
const getAudioBlob = async (audioId: string): Promise<Blob> => {
  const response = await fetch(`/api/audio/${audioId}`, {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  });
  
  if (!response.ok) {
    throw new Error('获取音频失败');
  }
  
  return await response.blob();
};

方案B:返回包含 Blob 的 JSON

json 复制代码
{
  "code": 200,
  "message": "success",
  "data": {
    "blob": "base64编码的音频数据",
    "mimeType": "audio/wav",
    "duration": 15.5,
    "fileName": "record-123.wav"
  }
}
typescript 复制代码
// 处理 base64 字符串转换为 Blob
const base64ToBlob = (base64: string, mimeType: string): Blob => {
  const byteCharacters = atob(base64);
  const byteNumbers = new Array(byteCharacters.length);
  
  for (let i = 0; i < byteCharacters.length; i++) {
    byteNumbers[i] = byteCharacters.charCodeAt(i);
  }
  
  const byteArray = new Uint8Array(byteNumbers);
  return new Blob([byteArray], { type: mimeType });
};

// 使用示例
const base64Response = await services.EasyFindController.getAudioBase64();
const blob = base64ToBlob(base64Response.data.blob, base64Response.data.mimeType);

4. 修改 AudioRecorder 组件以适应纯 Blob

typescript 复制代码
// 在现有 AudioRecorder 组件中修改
const AudioRecorder: React.FC<AudioRecorderProps> = ({
  value,
  onChange,
  // ... 其他 props
}) => {
  const [internalAudio, setInternalAudio] = useState<AudioData | null>(null);
  
  // 统一处理外部传入的 value
  useEffect(() => {
    if (value === undefined) return;
    
    const processValue = async () => {
      let processedAudio: AudioData | null = null;
      
      if (value === null) {
        processedAudio = null;
      } else if (value instanceof Blob) {
        // 处理纯 Blob
        const url = URL.createObjectURL(value);
        processedAudio = {
          blob: value,
          url,
          wavesurfer: null
        };
      } else if ('blob' in value && value.blob) {
        // 已经是 AudioData 类型
        if (!value.url && value.blob) {
          // 如果有 blob 但没有 url,生成 url
          const url = URL.createObjectURL(value.blob);
          processedAudio = {
            ...value,
            url,
            wavesurfer: null
          };
        } else {
          processedAudio = value as AudioData;
        }
      }
      
      setInternalAudio(processedAudio);
    };
    
    processValue();
  }, [value]);
  
  // 修改内部状态变化的处理
  const handleInternalChange = (newAudio: AudioData | null) => {
    setInternalAudio(newAudio);
    
    if (onChange) {
      // 根据需求决定返回什么
      if (newAudio) {
        // 返回完整的 AudioData
        onChange(newAudio);
        // 或者只返回 Blob
        // onChange(newAudio.blob);
      } else {
        onChange(null);
      }
    }
  };
  
  // 使用 internalAudio 替代原来的 audio 状态
  // 修改所有使用 audio 的地方为 internalAudio
  // ...
};

5. 完整示例:父组件使用

typescript 复制代码
// 父组件
const MyComponent: React.FC = () => {
  const [audioBlob, setAudioBlob] = useState<Blob | null>(null);
  
  // 从后端加载音频
  const loadAudio = async () => {
    try {
      // 方法1:直接获取 Blob
      const response = await fetch('/api/audio/123');
      const blob = await response.blob();
      setAudioBlob(blob);
      
      // 方法2:通过 API 服务获取
      // const result = await services.EasyFindController.getAudio(123);
      // setAudioBlob(result.data);
    } catch (error) {
      console.error('加载音频失败', error);
    }
  };
  
  // 处理音频变化
  const handleAudioChange = (audioDataOrBlob: AudioData | Blob | null) => {
    if (audioDataOrBlob instanceof Blob) {
      setAudioBlob(audioDataOrBlob);
      // 保存到后端
      saveAudioToBackend(audioDataOrBlob);
    } else if (audioDataOrBlob && 'blob' in audioDataOrBlob) {
      setAudioBlob(audioDataOrBlob.blob);
      // 保存到后端
      saveAudioToBackend(audioDataOrBlob.blob);
    } else {
      setAudioBlob(null);
      // 通知后端删除
      deleteAudioFromBackend();
    }
  };
  
  const saveAudioToBackend = async (blob: Blob) => {
    const formData = new FormData();
    formData.append('audio', blob, 'recording.wav');
    
    try {
      await services.EasyFindController.uploadAudio(formData);
    } catch (error) {
      console.error('上传音频失败', error);
    }
  };
  
  return (
    <div>
      <button onClick={loadAudio}>加载音频</button>
      
      <AudioRecorder
        value={audioBlob} // 直接传递 Blob
        onChange={handleAudioChange}
        onTranscriptionResult={(text) => {
          console.log('识别结果:', text);
        }}
      />
    </div>
  );
};

6. 建议的最佳实践

  1. 后端返回 Blob:简单直接,适合音频文件传输
  2. 前端统一处理:在父组件或 AudioRecorder 组件中将 Blob 转换为 AudioData
  3. 资源管理 :注意使用 URL.revokeObjectURL() 释放资源
  4. 类型安全:使用 TypeScript 类型保护确保类型安全
typescript 复制代码
// 类型保护函数
const isBlob = (value: any): value is Blob => {
  return value instanceof Blob;
};

const isAudioData = (value: any): value is AudioData => {
  return value && typeof value === 'object' && 'blob' in value;
};

这样设计可以保持组件的灵活性,既能处理完整的 AudioData 对象,也能处理纯 Blob 对象。

相关推荐
nie_xl1 小时前
VS/TRAE中设置本地maven地址的方法
运维·服务器·前端
LV技术派2 小时前
适合很多公司和团队的 AI Coding 落地范式(三)
前端·ai编程·cursor
一只小bit2 小时前
Qt 对话框全方面详解,包含示例与解析
前端·c++·qt·cpp·页面
m0_748254662 小时前
Angular 2 模板语法概述
前端·javascript·angular.js
专注VB编程开发20年2 小时前
EDGE估计没有switch到frame的做法
前端·edge·vba
_oP_i2 小时前
Chrome浏览器自动下载的AI模型文件
前端·chrome
小小前端--可笑可笑2 小时前
【Three.js + MediaPipe】视频粒子特效:实时运动检测与人物分割技术详解
开发语言·前端·javascript·音视频·粒子特效
摘星编程2 小时前
React Native for OpenHarmony 实战:Keyboard 键盘事件详解
react native·react.js·计算机外设
奔跑的web.2 小时前
JavaScript 对象属性遍历Object.entries Object.keys:6 种常用方法详解与对比
开发语言·前端·javascript·vue.js