HarmonyOS 鸿蒙Next中音频播放器如何实现快速播放

HarmonyOS 鸿蒙Next中音频播放器如何实现快速播放 有一段1-2s 的音频,如何实现每次点击都播放

现在在点击事件里,每次都需要等上一次播放完成才可以播放

cke_2599.png


更多关于HarmonyOS 鸿蒙Next中音频播放器如何实现快速播放的实战教程也可以访问 https://www.itying.com/category-93-b0.html

6 回复

AVPlayer设计用于较长音视频的播放,不支持低时延的多次并行播放。

推荐方案:使用SoundPool(特别是createParallelSoundPool)

SoundPool专为低时延短音频播放设计,支持一次加载、多次播放。而createParallelSoundPool创建的实例允许并行播放相同音频,无需等待上一次播放完成,正好满足您的需求。

实现步骤:

  1. 创建ParallelSoundPool实例:使用createParallelSoundPool创建音频池,设置最大流数(例如5),以支持并行播放。
  2. 加载音频资源:调用load方法加载音频资源(支持fd或uri方式),获取soundId
  3. 播放音频:在点击事件中,调用play方法播放音频,使用相同的soundId即可并行播放。

代码示例:

import { media } from '@kit.MediaKit';
import { audio } from '@kit.AudioKit';
import { BusinessError } from '@kit.BasicServicesKit';

// 定义变量
let soundPool: media.SoundPool | null = null;
let soundId: number = 0;

// 初始化SoundPool
async function initSoundPool() {
  try {
    const audioRendererInfo: audio.AudioRendererInfo = {
      usage: audio.StreamUsage.STREAM_USAGE_MUSIC, // 使用音乐流类型
      rendererFlags: 0
    };
    // 创建ParallelSoundPool,最大流数为5
    soundPool = await media.createParallelSoundPool(5, audioRendererInfo);
    console.info('Succeeded in creating ParallelSoundPool');

    // 加载音频资源(这里以fd方式为例,您也可以使用uri)
    // 假设您已经获取了文件描述符fd、offset和length
    let fd: number = ...; // 获取文件描述符
    let offset: number = ...; // 获取偏移量
    let length: number = ...; // 获取长度
    soundId = await soundPool.load(fd, offset, length);
    console.info(`Loaded audio, soundId: ${soundId}`);
  } catch (error) {
    console.error(`Failed to initialize SoundPool: ${(error as BusinessError).message}`);
  }
}

// 在点击事件中播放音频
function onClickPlay() {
  if (soundPool && soundId !== 0) {
    const playParameters: media.PlayParameters = {
      loop: 0, // 不循环,只播放一次
      rate: 1.0, // 正常速度
      leftVolume: 1.0, // 左声道音量
      rightVolume: 1.0, // 右声道音量
      priority: 0 // 优先级
    };
    soundPool.play(soundId, playParameters, (error: BusinessError | null, streamId: number) => {
      if (error) {
        console.error(`Play failed: error code ${error.code}, message ${error.message}`);
      } else {
        console.info(`Play success, streamId: ${streamId}`);
      }
    });
  } else {
    console.error('SoundPool not initialized or audio not loaded');
  }
}

// 初始化SoundPool(在组件初始化时调用)
initSoundPool();

注意事项:

  • 音频资源大小:SoundPool支持播放解码后1MB以下的音频资源。您的1-2秒音频应该符合要求。
  • 加载完成后再播放:确保在load完成回调后再调用play,否则播放可能失败。
  • 错误处理:添加错误监听器以处理异常,如文档中提到的on('error')
  • 资源释放:在不再需要时调用release方法释放资源,避免内存泄漏。

为什么不用AVPlayer?

  • AVPlayer设计用于较长音视频播放,状态管理严格(如需要prepare、reset等),不适合低时延多次播放。
  • AVPlayer播放同一资源时,通常需要等待上一次播放完成或手动重置,无法实现并行播放。

更多关于HarmonyOS 鸿蒙Next中音频播放器如何实现快速播放的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


大佬,如果使用URl 的音频呢,

网络地址,url 爱楼,

使用SoundPool实现网络音频的即时播放

以下是完整的实现方案,包括下载网络音频、使用URI加载到SoundPool,以及支持每次点击即时播放。

完整代码示例

import { media } from '@kit.MediaKit';
import { audio } from '@kit.AudioKit';
import { request } from '@kit.BasicServicesKit';
import { fileIo } from '@kit.CoreFileKit';
import { common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';

// 定义全局变量
let soundPool: media.SoundPool | null = null;
let soundIds: number[] = []; // 存储多个soundId以实现并行播放
let currentIndex: number = 0;
let context: common.UIAbilityContext;

@Component
struct Index {
  private soundPoolManager: SoundPoolManager = new SoundPoolManager();

  aboutToAppear() {
    // 获取UIAbilityContext
    context = this.getUIContext().getHostContext() as common.UIAbilityContext;
    // 初始化SoundPool并加载网络音频
    this.soundPoolManager.initializeSoundPool(context, ' https://example.com/audio/short_audio.mp3 ');
  }

  build() {
    Column() {
      Button('播放音频')
        .onClick(() => {
          // 每次点击都会立即播放音频
          this.soundPoolManager.playSound();
        })
        .margin(20)
        .width('80%')
        .height(50)
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

// SoundPool管理类
class SoundPoolManager {
  private context: common.UIAbilityContext | null = null;
  private audioFileFd: number | null = null;
  private readonly MAX_INSTANCES = 5; // 预加载的音频实例数量

  // 初始化SoundPool并加载网络音频
  async initializeSoundPool(context: common.UIAbilityContext, networkUrl: string) {
    this.context = context;
    
    try {
      // 1. 下载网络音频到本地
      const localFilePath = await this.downloadAudio(networkUrl);
      
      // 2. 创建SoundPool实例
      const audioRendererInfo: audio.AudioRendererInfo = {
        usage: audio.StreamUsage.STREAM_USAGE_MUSIC,
        rendererFlags: 0
      };
      
      soundPool = await media.createSoundPool(this.MAX_INSTANCES, audioRendererInfo);
      console.info('SoundPool创建成功');
      
      // 3. 获取文件描述符并生成URI
      const file = await fileIo.open(localFilePath, fileIo.OpenMode.READ_ONLY);
      this.audioFileFd = file.fd;
      const uri = `fd://${this.audioFileFd}`;
      
      // 4. 预加载多个音频实例(用于并行播放)
      for (let i = 0; i < this.MAX_INSTANCES; i++) {
        try {
          const soundId = await soundPool.load(uri);
          soundIds.push(soundId);
          console.info(`音频加载成功,soundId: ${soundId}`);
        } catch (loadError) {
          console.error(`加载音频实例失败: ${(loadError as BusinessError).message}`);
        }
      }
    } catch (error) {
      console.error(`初始化SoundPool失败: ${(error as BusinessError).message}`);
    }
  }

  // 下载网络音频到本地沙箱
  private async downloadAudio(url: string): Promise<string> {
    if (!this.context) {
      throw new Error('Context未初始化');
    }

    const localFileName = 'cached_audio.mp3';
    const dir = this.context.filesDir;
    const localPath = `${dir}/${localFileName}`;

    return new Promise<string>((resolve, reject) => {
      const config: request.agent.Config = {
        action: request.agent.Action.DOWNLOAD,
        url: url,
        saveas: localPath,
        overwrite: true,
        gauge: true
      };

      request.agent.create(this.context, (err, agent) => {
        if (err) {
          reject(err);
          return;
        }

        agent.request(config, (err, response) => {
          if (err) {
            reject(err);
            return;
          }
          console.info('音频下载完成,路径: ' + localPath);
          resolve(localPath);
        });
      });
    });
  }

  // 播放音频
  playSound() {
    if (!soundPool || soundIds.length === 0) {
      console.error('SoundPool未初始化或音频未加载');
      return;
    }

    // 轮流使用不同的soundId以实现并行播放
    const soundId = soundIds[currentIndex];
    currentIndex = (currentIndex + 1) % soundIds.length;

    const playParameters: media.PlayParameters = {
      loop: 0, // 不循环
      rate: 1.0, // 正常速度
      leftVolume: 1.0, // 左声道音量
      rightVolume: 1.0, // 右声道音量
      priority: 0 // 优先级
    };

    soundPool.play(soundId, playParameters, (error: BusinessError | null, streamId: number) => {
      if (error) {
        console.error(`播放失败: ${error.code}, ${error.message}`);
      } else {
        console.info(`播放成功, streamId: ${streamId}`);
      }
    });
  }

  // 释放资源
  async release() {
    if (soundPool) {
      try {
        // 释放所有音频资源
        for (const soundId of soundIds) {
          await soundPool.unload(soundId);
        }
        soundIds = [];
        
        // 释放SoundPool
        await soundPool.release();
        soundPool = null;
        
        // 关闭文件描述符
        if (this.audioFileFd !== null) {
          await fileIo.close(this.audioFileFd);
          this.audioFileFd = null;
        }
        
        console.info('资源已释放');
      } catch (error) {
        console.error(`释放资源失败: ${(error as BusinessError).message}`);
      }
    }
  }
}

配置权限

在项目的module.json5文件中添加以下权限:

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET",
        "reason": "需要访问网络下载音频文件"
      },
      {
        "name": "ohos.permission.WRITE_USER_STORAGE",
        "reason": "需要将音频文件保存到本地"
      },
      {
        "name": "ohos.permission.READ_USER_STORAGE",
        "reason": "需要读取本地音频文件"
      }
    ]
  }
}

实现原理

  1. 网络音频下载:使用request.agent下载网络音频到应用沙箱目录
  2. URI生成:通过fileIo.open获取文件描述符(fd),生成fd://格式的URI
  3. SoundPool初始化:创建SoundPool实例并预加载多个音频实例
  4. 并行播放:通过轮流使用不同的soundId实现同时播放多个音频实例

注意事项

  1. 音频格式支持:SoundPool支持MP3、WAV、OGG等格式,但建议使用压缩比较高的短音频
  2. 内存管理:预加载多个实例会占用更多内存,请根据实际需要调整MAX_INSTANCES
  3. 资源释放:在页面销毁或不再需要时调用release()方法释放资源
  4. 错误处理:添加了完整的错误处理机制,确保应用稳定性

优化建议

  1. 对于极短音频(1-2秒),可以适当增加预加载实例数量(如10个)
  2. 可以考虑添加加载状态提示,提升用户体验
  3. 如果音频内容可能变化,可以添加缓存机制和定期更新策略

在HarmonyOS鸿蒙Next中,音频播放器通过AVPlayer组件实现快速播放。使用setPlaybackSpeed(float speed)方法调整播放速率,参数speed支持0.5至2.0倍速范围。需先调用prepare()完成资源加载,再通过play()启动播放。示例代码:

let avPlayer = await media.createAVPlayer();
avPlayer.setPlaybackSpeed(1.5); // 设置1.5倍速
avPlayer.play();

在HarmonyOS Next中,可以通过重置音频播放状态或创建新的播放实例来实现快速连续播放。使用AVPlayer时,建议在每次点击时调用reset()方法重置播放器,然后重新设置数据源并调用prepare()play()。这样可以避免等待上一次播放完成,实现即时响应。代码示例如下:

import media from '@ohos.multimedia.media';

let avPlayer: media.AVPlayer | null = null;

// 初始化播放器
async function initPlayer(src: string) {
  avPlayer = await media.createAVPlayer();
  avPlayer.reset(); // 确保初始状态
  avPlayer.url = src;
  await avPlayer.prepare();
}

// 点击事件处理
async function onPlayClick() {
  if (avPlayer) {
    avPlayer.reset(); // 重置以中断当前播放
    await avPlayer.prepare(); // 重新准备
    avPlayer.play(); // 立即播放
  }
}

// 使用示例
initPlayer('音频资源路径');

注意:频繁重置可能增加资源开销,但适用于短音频场景。

回到顶部