小程序使用音频

微信小程序提供了强大的音频处理能力,包括音频播放、录制、控制等功能。主要涉及以下核心 API:

主要音频 API:

​wx.chooseMessageFile​ - 选择音频文件

​wx.createInnerAudioContext​ - 创建音频上下文

​wx.getBackgroundAudioManager​ - 后台音频管理

​wx.recorderManager​ - 音频录制管理

​wx.saveFile​ - 保存音频文件

音频组件特性:

支持多种音频格式(mp3, aac, wav, ogg 等)

支持播放控制(播放/暂停、进度、音量等)

支持后台播放

支持音频可视化

支持音频录制和编辑

这是一个 JS 文件示例:

// pages/audio/audio.js
Page({
  data: {
    // 音频列表
    audioList: [],
    // 当前播放的音频索引
    currentAudioIndex: -1,
    // 音频上下文
    audioContext: null,
    // 播放状态
    isPlaying: false,
    // 播放进度
    currentTime: 0,
    // 音频总时长
    duration: 0,
    // 播放进度百分比
    progress: 0,
    // 音量 (0-1)
    volume: 0.7,
    // 播放速率 (0.5-2.0)
    playbackRate: 1.0,
    // 是否循环播放
    isLooping: false,
    // 是否静音
    isMuted: false,
    // 加载状态
    isLoading: false,
    // 错误信息
    errorMsg: '',
    // 音频信息
    audioInfo: {
      name: '',
      size: 0,
      duration: 0,
      format: ''
    },
    // 播放模式 (顺序/随机/单曲循环)
    playMode: 'sequential', // sequential, random, single
    // 播放历史
    playHistory: [],
    // 音频频谱数据(用于可视化)
    audioSpectrum: [],
    // 是否显示音频可视化
    showVisualization: false
  },

  onLoad() {
    console.log('音频页面加载');
    this.initAudioContext();
    this.loadAudioListFromStorage();
  },

  onUnload() {
    // 页面卸载时停止播放
    if (this.data.audioContext) {
      this.data.audioContext.stop();
      this.data.audioContext.destroy();
    }
  },

  onHide() {
    // 页面隐藏时暂停播放
    if (this.data.isPlaying) {
      this.pauseAudio();
    }
  },

  onShow() {
    // 页面显示时恢复播放状态
    this.updatePlaybackStatus();
  },

  /**
   * 初始化音频上下文
   */
  initAudioContext() {
    const audioContext = wx.createInnerAudioContext();
    audioContext.obeyMuteSwitch = false; // 不受静音键影响

    // 设置事件监听
    audioContext.onPlay(() => {
      console.log('音频开始播放');
      this.setData({
        isPlaying: true,
        errorMsg: ''
      });
    });

    audioContext.onPause(() => {
      console.log('音频暂停');
      this.setData({
        isPlaying: false
      });
    });

    audioContext.onStop(() => {
      console.log('音频停止');
      this.setData({
        isPlaying: false,
        currentTime: 0,
        progress: 0
      });
    });

    audioContext.onEnded(() => {
      console.log('音频播放结束');
      this.setData({
        isPlaying: false,
        currentTime: 0,
        progress: 0
      });
      this.playNextAudio();
    });

    audioContext.onTimeUpdate(() => {
      const currentTime = audioContext.currentTime;
      const duration = audioContext.duration;
      const progress = duration > 0 ? (currentTime / duration * 100) : 0;

      this.setData({
        currentTime: currentTime,
        duration: duration,
        progress: progress
      });

      // 更新音频可视化数据
      this.updateAudioVisualization();
    });

    audioContext.onWaiting(() => {
      console.log('音频加载中');
      this.setData({
        isLoading: true
      });
    });

    audioContext.onCanPlay(() => {
      console.log('音频可以播放');
      this.setData({
        isLoading: false
      });
    });

    audioContext.onError((res) => {
      console.error('音频播放错误', res);
      this.handleAudioError(res);
    });

    audioContext.onSeeked(() => {
      console.log('音频跳转完成');
    });

    this.setData({
      audioContext: audioContext
    });
  },

  /**
   * 从本地存储加载音频列表
   */
  loadAudioListFromStorage() {
    try {
      const audioList = wx.getStorageSync('audioList') || [];
      this.setData({
        audioList: audioList
      });
      console.log('音频列表加载成功', audioList.length);
    } catch (error) {
      console.error('加载音频列表失败', error);
    }
  },

  /**
   * 保存音频列表到本地存储
   */
  saveAudioListToStorage() {
    try {
      wx.setStorageSync('audioList', this.data.audioList);
      console.log('音频列表保存成功');
    } catch (error) {
      console.error('保存音频列表失败', error);
    }
  },

  /**
   * 选择音频文件
   */
  chooseAudioFile() {
    wx.chooseMessageFile({
      count: 5, // 最多选择5个
      type: 'file',
      extension: ['mp3', 'aac', 'wav', 'm4a', 'ogg'], // 支持的音频格式
      success: (res) => {
        console.log('选择音频文件成功', res);

        const tempFiles = res.tempFiles;
        const newAudioList = tempFiles.map((file, index) => ({
          tempFilePath: file.path,
          name: file.name,
          size: file.size,
          duration: 0, // 需要后续获取
          format: this.getFileFormat(file.name),
          id: Date.now() + index,
          createTime: new Date().toLocaleString(),
          isLocal: false
        }));

        // 获取音频时长信息
        this.getAudioDurations(newAudioList).then(audiosWithDuration => {
          this.setData({
            audioList: [...this.data.audioList, ...audiosWithDuration],
            errorMsg: ''
          });
          this.saveAudioListToStorage();

          wx.showToast({
            title: `成功选择${newAudioList.length}个音频文件`,
            icon: 'success',
            duration: 2000
          });
        });

      },
      fail: (err) => {
        console.error('选择音频文件失败', err);
        this.handleAudioError(err, '选择音频文件');
      }
    });
  },

  /**
   * 获取文件格式
   */
  getFileFormat(filename) {
    const formats = {
      'mp3': 'MP3',
      'aac': 'AAC',
      'wav': 'WAV',
      'm4a': 'M4A',
      'ogg': 'OGG',
      'flac': 'FLAC'
    };

    const ext = filename.split('.').pop().toLowerCase();
    return formats[ext] || ext.toUpperCase();
  },

  /**
   * 获取音频时长
   */
  getAudioDurations(audioList) {
    const promises = audioList.map(audio => {
      return new Promise((resolve) => {
        const tempAudio = wx.createInnerAudioContext();
        tempAudio.src = audio.tempFilePath;

        tempAudio.onCanPlay(() => {
          setTimeout(() => {
            const duration = tempAudio.duration || 0;
            tempAudio.destroy();
            resolve({
              ...audio,
              duration: duration
            });
          }, 100);
        });

        tempAudio.onError(() => {
          tempAudio.destroy();
          resolve({
            ...audio,
            duration: 0
          });
        });

        // 触发加载
        tempAudio.play();
        setTimeout(() => {
          tempAudio.pause();
        }, 50);
      });
    });

    return Promise.all(promises);
  },

  /**
   * 录制音频
   */
  startRecording() {
    if (this.recorderManager) {
      this.stopRecording();
    }

    this.recorderManager = wx.getRecorderManager();

    this.recorderManager.onStart(() => {
      console.log('开始录音');
      this.setData({
        isRecording: true,
        recordingTime: 0
      });

      // 开始计时
      this.recordingTimer = setInterval(() => {
        this.setData({
          recordingTime: this.data.recordingTime + 1
        });
      }, 1000);
    });

    this.recorderManager.onStop((res) => {
      console.log('录音结束', res);
      this.setData({
        isRecording: false
      });

      if (this.recordingTimer) {
        clearInterval(this.recordingTimer);
      }

      if (res.tempFilePath) {
        this.saveRecordedAudio(res.tempFilePath);
      }
    });

    this.recorderManager.onError((err) => {
      console.error('录音错误', err);
      this.setData({
        isRecording: false,
        errorMsg: '录音失败'
      });

      if (this.recordingTimer) {
        clearInterval(this.recordingTimer);
      }
    });

    // 开始录音
    this.recorderManager.start({
      duration: 60000, // 最长60秒
      sampleRate: 44100,
      numberOfChannels: 1,
      encodeBitRate: 192000,
      format: 'aac'
    });
  },

  /**
   * 停止录音
   */
  stopRecording() {
    if (this.recorderManager) {
      this.recorderManager.stop();
    }
  },

  /**
   * 保存录制的音频
   */
  saveRecordedAudio(tempFilePath) {
    const newAudio = {
      tempFilePath: tempFilePath,
      name: `录音_${new Date().toLocaleString()}`,
      size: 0,
      duration: this.data.recordingTime,
      format: 'AAC',
      id: Date.now(),
      createTime: new Date().toLocaleString(),
      isRecorded: true
    };

    this.setData({
      audioList: [newAudio, ...this.data.audioList]
    });
    this.saveAudioListToStorage();

    wx.showToast({
      title: '录音保存成功',
      icon: 'success'
    });
  },

  /**
   * 播放音频
   */
  playAudio(index) {
    if (index < 0 || index >= this.data.audioList.length) {
      wx.showToast({
        title: '音频不存在',
        icon: 'none'
      });
      return;
    }

    const audio = this.data.audioList[index];

    if (!audio.tempFilePath) {
      wx.showToast({
        title: '音频文件无效',
        icon: 'none'
      });
      return;
    }

    this.setData({
      currentAudioIndex: index,
      isLoading: true,
      errorMsg: ''
    });

    // 设置音频源
    this.data.audioContext.src = audio.tempFilePath;
    this.data.audioContext.startTime = 0;

    // 应用当前设置
    this.data.audioContext.volume = this.data.volume;
    this.data.audioContext.playbackRate = this.data.playbackRate;
    this.data.audioContext.loop = this.data.isLooping;

    // 开始播放
    this.data.audioContext.play();

    // 更新音频信息
    this.setData({
      audioInfo: {
        name: audio.name,
        size: audio.size,
        duration: audio.duration,
        format: audio.format
      }
    });

    // 添加到播放历史
    this.addToPlayHistory(index);
  },

  /**
   * 暂停播放
   */
  pauseAudio() {
    if (this.data.audioContext) {
      this.data.audioContext.pause();
    }
  },

  /**
   * 停止播放
   */
  stopAudio() {
    if (this.data.audioContext) {
      this.data.audioContext.stop();
    }
  },

  /**
   * 切换播放/暂停
   */
  togglePlayPause() {
    if (this.data.currentAudioIndex === -1) {
      if (this.data.audioList.length > 0) {
        this.playAudio(0); // 播放第一个音频
      } else {
        wx.showToast({
          title: '请先选择音频文件',
          icon: 'none'
        });
      }
      return;
    }

    if (this.data.isPlaying) {
      this.pauseAudio();
    } else {
      this.playAudio(this.data.currentAudioIndex);
    }
  },

  /**
   * 跳转到指定时间
   */
  seekTo(time) {
    if (this.data.audioContext) {
      this.data.audioContext.seek(time);
      this.setData({
        currentTime: time
      });
    }
  },

  /**
   * 调整音量
   */
  changeVolume(volume) {
    const newVolume = Math.max(0, Math.min(1, volume));
    this.setData({
      volume: newVolume
    });

    if (this.data.audioContext) {
      this.data.audioContext.volume = newVolume;
    }
  },

  /**
   * 调整播放速率
   */
  changePlaybackRate(rate) {
    const newRate = Math.max(0.5, Math.min(2.0, rate));
    this.setData({
      playbackRate: newRate
    });

    if (this.data.audioContext) {
      this.data.audioContext.playbackRate = newRate;
    }

    wx.showToast({
      title: `播放速率: ${newRate}x`,
      icon: 'none',
      duration: 1000
    });
  },

  /**
   * 切换循环播放
   */
  toggleLoop() {
    const newLoop = !this.data.isLooping;
    this.setData({
      isLooping: newLoop
    });

    if (this.data.audioContext) {
      this.data.audioContext.loop = newLoop;
    }

    wx.showToast({
      title: newLoop ? '循环播放已开启' : '循环播放已关闭',
      icon: 'none',
      duration: 1000
    });
  },

  /**
   * 切换静音
   */
  toggleMute() {
    const newMuted = !this.data.isMuted;
    this.setData({
      isMuted: newMuted
    });

    if (this.data.audioContext) {
      this.data.audioContext.volume = newMuted ? 0 : this.data.volume;
    }
  },

  /**
   * 播放下一首
   */
  playNextAudio() {
    if (this.data.audioList.length === 0) return;

    let nextIndex;
    switch (this.data.playMode) {
      case 'sequential':
        nextIndex = (this.data.currentAudioIndex + 1) % this.data.audioList.length;
        break;
      case 'random':
        nextIndex = Math.floor(Math.random() * this.data.audioList.length);
        break;
      case 'single':
        nextIndex = this.data.currentAudioIndex;
        break;
      default:
        nextIndex = (this.data.currentAudioIndex + 1) % this.data.audioList.length;
    }

    this.playAudio(nextIndex);
  },

  /**
   * 播放上一首
   */
  playPrevAudio() {
    if (this.data.audioList.length === 0) return;

    let prevIndex;
    if (this.data.currentAudioIndex <= 0) {
      prevIndex = this.data.audioList.length - 1;
    } else {
      prevIndex = this.data.currentAudioIndex - 1;
    }

    this.playAudio(prevIndex);
  },

  /**
   * 切换播放模式
   */
  changePlayMode() {
    const modes = ['sequential', 'random', 'single'];
    const currentIndex = modes.indexOf(this.data.playMode);
    const nextMode = modes[(currentIndex + 1) % modes.length];

    this.setData({
      playMode: nextMode
    });

    const modeNames = {
      'sequential': '顺序播放',
      'random': '随机播放',
      'single': '单曲循环'
    };

    wx.showToast({
      title: modeNames[nextMode],
      icon: 'none',
      duration: 1000
    });
  },

  /**
   * 添加到播放历史
   */
  addToPlayHistory(index) {
    const audio = this.data.audioList[index];
    const history = this.data.playHistory.filter(item => item.id !== audio.id);
    history.unshift({
      id: audio.id,
      name: audio.name,
      index: index,
      playTime: new Date().toLocaleString()
    });

    // 只保留最近20条记录
    if (history.length > 20) {
      history.pop();
    }

    this.setData({
      playHistory: history
    });
  },

  /**
   * 更新播放状态
   */
  updatePlaybackStatus() {
    if (this.data.audioContext) {
      const audioContext = this.data.audioContext;
      this.setData({
        isPlaying: !audioContext.paused,
        currentTime: audioContext.currentTime,
        duration: audioContext.duration,
        progress: audioContext.duration > 0 ?
          (audioContext.currentTime / audioContext.duration * 100) : 0
      });
    }
  },

  /**
   * 更新音频可视化
   */
  updateAudioVisualization() {
    if (!this.data.showVisualization) return;

    // 模拟频谱数据(实际项目中需要使用音频分析API)
    const spectrum = [];
    for (let i = 0; i < 32; i++) {
      spectrum.push(Math.random() * 100);
    }

    this.setData({
      audioSpectrum: spectrum
    });
  },

  /**
   * 切换音频可视化
   */
  toggleVisualization() {
    this.setData({
      showVisualization: !this.data.showVisualization
    });
  },

  /**
   * 处理音频错误
   */
  handleAudioError(error, operation = '播放音频') {
    let errorMsg = `${operation}失败`;

    if (error && error.errMsg) {
      switch (error.errMsg) {
        case 'MEDIA_ERR_NETWORK':
          errorMsg = '网络错误,请检查网络连接';
          break;
        case 'MEDIA_ERR_DECODE':
          errorMsg = '音频解码错误,文件可能已损坏';
          break;
        case 'MEDIA_ERR_SRC_NOT_SUPPORTED':
          errorMsg = '不支持的音频格式';
          break;
        case 'chooseMessageFile:fail cancel':
          errorMsg = '用户取消了选择';
          return; // 不显示错误提示
        default:
          errorMsg = error.errMsg;
      }
    }

    this.setData({
      errorMsg: errorMsg,
      isLoading: false,
      isPlaying: false
    });

    wx.showToast({
      title: errorMsg,
      icon: 'none',
      duration: 2000
    });
  },

  /**
   * 删除音频
   */
  deleteAudio(index) {
    if (index < 0 || index >= this.data.audioList.length) return;

    wx.showModal({
      title: '确认删除',
      content: '确定要删除这个音频文件吗?',
      success: (res) => {
        if (res.confirm) {
          const audioToDelete = this.data.audioList[index];
          const newAudioList = this.data.audioList.filter((_, i) => i !== index);

          let newCurrentIndex = this.data.currentAudioIndex;

          // 调整当前播放索引
          if (index === this.data.currentAudioIndex) {
            this.stopAudio();
            newCurrentIndex = -1;
          } else if (index < this.data.currentAudioIndex) {
            newCurrentIndex = this.data.currentAudioIndex - 1;
          }

          this.setData({
            audioList: newAudioList,
            currentAudioIndex: newCurrentIndex
          });

          this.saveAudioListToStorage();

          wx.showToast({
            title: '删除成功',
            icon: 'success'
          });
        }
      }
    });
  },

  /**
   * 清空所有音频
   */
  clearAllAudios() {
    if (this.data.audioList.length === 0) return;

    wx.showModal({
      title: '确认清空',
      content: '确定要清空所有音频文件吗?',
      success: (res) => {
        if (res.confirm) {
          this.setData({
            audioList: [],
            currentAudioIndex: -1
          });
          this.stopAudio();
          this.saveAudioListToStorage();

          wx.showToast({
            title: '清空成功',
            icon: 'success'
          });
        }
      }
    });
  },

  /**
   * 保存音频到本地
   */
  saveAudioToLocal(index) {
    if (index === undefined) {
      index = this.data.currentAudioIndex;
    }

    if (index === -1) {
      wx.showToast({
        title: '请先选择音频',
        icon: 'none'
      });
      return;
    }

    const audio = this.data.audioList[index];

    wx.saveFile({
      tempFilePath: audio.tempFilePath,
      success: (res) => {
        console.log('音频保存成功', res);

        // 更新音频列表,标记为已保存
        const newAudioList = [...this.data.audioList];
        newAudioList[index].savedFilePath = res.savedFilePath;
        newAudioList[index].isLocal = true;

        this.setData({
          audioList: newAudioList
        });
        this.saveAudioListToStorage();

        wx.showToast({
          title: '保存成功',
          icon: 'success'
        });
      },
      fail: (err) => {
        console.error('保存音频失败', err);
        wx.showToast({
          title: '保存失败',
          icon: 'none'
        });
      }
    });
  },

  /**
   * 分享音频
   */
  onShareAppMessage() {
    if (this.data.currentAudioIndex !== -1) {
      const currentAudio = this.data.audioList[this.data.currentAudioIndex];
      return {
        title: `分享音频: ${currentAudio.name}`,
        path: '/pages/audio/audio'
      };
    }

    return {
      title: '分享我的音频库',
      path: '/pages/audio/audio'
    };
  },

  /**
   * 格式化时间(秒 -> 分:秒)
   */
  formatTime(seconds) {
    if (isNaN(seconds)) return '0:00';

    const mins = Math.floor(seconds / 60);
    const secs = Math.floor(seconds % 60);
    return `${mins}:${secs < 10 ? '0' : ''}${secs}`;
  },

  /**
   * 格式化文件大小
   */
  formatFileSize(bytes) {
    if (!bytes || bytes === 0) return '0 B';
    const k = 1024;
    const sizes = ['B', 'KB', 'MB', 'GB'];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
  },

  /**
   * 创建播放列表
   */
  createPlaylist(name, audioIndexes) {
    const playlist = {
      id: Date.now(),
      name: name,
      createTime: new Date().toLocaleString(),
      audioIndexes: audioIndexes,
      count: audioIndexes.length
    };

    // 保存播放列表到本地存储
    try {
      const playlists = wx.getStorageSync('playlists') || [];
      playlists.push(playlist);
      wx.setStorageSync('playlists', playlists);

      wx.showToast({
        title: '播放列表创建成功',
        icon: 'success'
      });
    } catch (error) {
      console.error('保存播放列表失败', error);
    }
  },

  /**
   * 导入网络音频
   */
  importNetworkAudio(url) {
    wx.showLoading({
      title: '下载中...',
      mask: true
    });

    wx.downloadFile({
      url: url,
      success: (res) => {
        wx.hideLoading();
        if (res.statusCode === 200) {
          const newAudio = {
            tempFilePath: res.tempFilePath,
            name: '网络音频',
            size: res.totalBytes,
            duration: 0,
            format: 'MP3',
            id: Date.now(),
            createTime: new Date().toLocaleString(),
            isFromNetwork: true
          };

          this.setData({
            audioList: [newAudio, ...this.data.audioList]
          });
          this.saveAudioListToStorage();

          wx.showToast({
            title: '导入成功',
            icon: 'success'
          });
        }
      },
      fail: (err) => {
        wx.hideLoading();
        wx.showToast({
          title: '导入失败',
          icon: 'none'
        });
      }
    });
  },

  /**
   * 音频搜索
   */
  searchAudio(keyword) {
    if (!keyword) {
      return this.data.audioList;
    }

    return this.data.audioList.filter(audio =>
      audio.name.toLowerCase().includes(keyword.toLowerCase())
    );
  },

  /**
   * 获取音频统计信息
   */
  getAudioStats() {
    const totalCount = this.data.audioList.length;
    const totalDuration = this.data.audioList.reduce((sum, audio) =>
      sum + (audio.duration || 0), 0
    );
    const totalSize = this.data.audioList.reduce((sum, audio) =>
      sum + (audio.size || 0), 0
    );

    return {
      totalCount,
      totalDuration: this.formatTime(totalDuration),
      totalSize: this.formatFileSize(totalSize),
      averageDuration: this.formatTime(totalDuration / totalCount)
    };
  }
});

这是一个 WXML 文件示例:

<!-- pages/audio/audio.wxml -->
<view class="container">
  <!-- 操作按钮区域 -->
  <view class="action-buttons">
    <button bindtap="chooseAudioFile">选择音频</button>
    <button bindtap="startRecording" wx:if="{{!isRecording}}">开始录音</button>
    <button bindtap="stopRecording" wx:if="{{isRecording}}">停止录音</button>
    <button bindtap="togglePlayPause">{{isPlaying ? '暂停' : '播放'}}</button>
    <button bindtap="stopAudio">停止</button>
    <button bindtap="playPrevAudio">上一首</button>
    <button bindtap="playNextAudio">下一首</button>
    <button bindtap="changePlayMode">播放模式</button>
  </view>

  <!-- 播放控制区域 -->
  <view class="player-controls" wx:if="{{currentAudioIndex !== -1}}">
    <view class="audio-info">
      <text>正在播放: {{audioInfo.name}}</text>
      <text>格式: {{audioInfo.format}} | 时长: {{formatTime(duration)}}</text>
    </view>

    <view class="progress-control">
      <text>{{formatTime(currentTime)}}</text>
      <slider
        value="{{currentTime}}"
        max="{{duration}}"
        step="1"
        bindchange="seekTo"
        activeColor="#007AFF"
      />
      <text>{{formatTime(duration)}}</text>
    </view>

    <view class="control-buttons">
      <button bindtap="toggleLoop">{{isLooping ? '关闭循环' : '开启循环'}}</button>
      <button bindtap="toggleMute">{{isMuted ? '取消静音' : '静音'}}</button>
      <button bindtap="changeVolume(0.5)">音量50%</button>
      <button bindtap="changePlaybackRate(1.5)">1.5倍速</button>
    </view>

    <!-- 音频可视化 -->
    <view class="visualization" wx:if="{{showVisualization}}">
      <view class="spectrum-bars">
        <view
          class="spectrum-bar"
          wx:for="{{audioSpectrum}}"
          wx:key="index"
          style="height: {{item}}px"
        ></view>
      </view>
    </view>
  </view>

  <!-- 音频列表 -->
  <view class="audio-list">
    <view class="audio-item"
      wx:for="{{audioList}}"
      wx:key="id"
      class="{{index === currentAudioIndex ? 'active' : ''}}"
    >
      <view class="audio-info" bindtap="playAudio" data-index="{{index}}">
        <text class="audio-name">{{item.name}}</text>
        <text class="audio-details">
          {{formatTime(item.duration)}} | {{formatFileSize(item.size)}} | {{item.format}}
        </text>
      </view>
      <view class="audio-actions">
        <button bindtap="deleteAudio" data-index="{{index}}">删除</button>
        <button bindtap="saveAudioToLocal" data-index="{{index}}">保存</button>
      </view>
    </view>
  </view>

  <!-- 状态显示 -->
  <view class="status-info">
    <text>共 {{audioList.length}} 个音频文件</text>
    <text>播放模式: {{playMode === 'sequential' ? '顺序' : playMode === 'random' ? '随机' : '单曲'}}</text>
    <text wx:if="{{errorMsg}}">错误: {{errorMsg}}</text>
  </view>

  <!-- 加载指示器 -->
  <view wx:if="{{isLoading}}" class="loading">
    <text>加载中...</text>
  </view>
</view>

主要功能说明

  1. ​ 音频选择功能 ​

chooseAudioFile(): 选择音频文件

startRecording(): 开始录音

stopRecording(): 停止录音

支持多种音频格式(mp3, aac, wav, ogg 等)

  1. ​ 音频播放控制 ​

playAudio(): 播放指定音频

pauseAudio(): 暂停播放

stopAudio(): 停止播放

togglePlayPause(): 播放/暂停切换

seekTo(): 跳转到指定时间

  1. ​ 播放器功能 ​

changeVolume(): 调整音量

changePlaybackRate(): 调整播放速率

toggleLoop(): 切换循环播放

toggleMute(): 切换静音

changePlayMode(): 切换播放模式

  1. ​ 播放列表管理 ​

playNextAudio(): 播放下一首

playPrevAudio(): 播放上一首

支持顺序播放、随机播放、单曲循环

播放历史记录

  1. ​ 音频可视化 ​

updateAudioVisualization(): 更新音频频谱

toggleVisualization(): 显示/隐藏可视化

模拟音频频谱数据(实际项目可使用 Web Audio API)

  1. ​ 文件管理 ​

deleteAudio(): 删除音频文件

clearAllAudios(): 清空所有音频

saveAudioToLocal(): 保存音频到本地

本地存储管理

  1. ​ 错误处理 ​

handleAudioError(): 统一错误处理

网络错误、解码错误、格式错误等处理

友好的错误提示

  1. ​ 工具函数 ​

formatTime(): 时间格式化

formatFileSize(): 文件大小格式化

getAudioStats(): 获取音频统计信息

  1. ​ 高级功能 ​

音频录制和保存

网络音频导入

播放列表创建

音频搜索功能

后台播放支持

这个示例提供了完整的音频功能实现,包括音频选择、播放控制、文件管理、可视化等功能,可以直接在小程序项目中使用。

通关密语:音频