鸿蒙学习实战之路-AVPlayer音频播放完全指南

最近好多朋友问我:“西兰花啊,我想在鸿蒙应用里加个音频播放功能,可看了官方文档有点懵,咋弄啊?” 害,这问题可问对人了!今天我就手把手带你用AVPlayer实现音频播放功能,从买"播放机"到"放音乐",全程不超过20分钟~


🥦 先唠唠AVPlayer是啥

AVPlayer就像咱们家里的音乐播放机,能播放各种音频(和视频)资源。用它你可以:

  • 播放本地音乐文件
  • 播放网络音乐
  • 控制播放(播放/暂停/跳转/停止)
  • 调整音量、倍速
  • 监听播放状态变化

播放的全流程就像放CD:

  1. 买播放机(创建AVPlayer实例)
  2. 接好电源和音箱(设置监听事件)
  3. 放CD(设置播放资源)
  4. 调整音效(设置音频渲染)
  5. 预热(准备播放)
  6. 开始播放(播控操作)
  7. 换CD(更换资源)
  8. 关机(释放资源)

看看官方给的状态变化图,是不是超清晰?


🥦 西兰花警告

在开始之前,先说说开发建议和注意事项:

必看的开发建议

  • ✅ 要实现后台/熄屏播放,需要接入AVSession(媒体会话)申请长时任务,避免被系统打断
  • ✅ 要处理音频冲突,建议监听音频打断事件,别让用户听着听着突然没声音了
  • ✅ 要播放网络音乐,必须申请ohos.permission.INTERNET权限(就像听网络电台要连Wi-Fi)
  • ✅ 要切换听筒/扬声器,参考音频输出设备路由切换
  • ✅ 要监听设备变化,用on('audioOutputDeviceChangeWithInfo')监听音频输出设备变化

状态管理要注意

当播放处于prepared/playing/paused/completed状态时,播放引擎处于工作状态,会占用大量内存。不用的时候一定要调用reset()release()释放资源,就像不用播放机时要关机一样!


第一步:买播放机(创建AVPlayer实例)

要播放音乐得先有播放机,要实现音频播放得先创建AVPlayer实例:

import { media } from '@kit.MediaKit';

// 创建AVPlayer实例(买了个音乐播放机)
let 音乐播放机 = await media.createAVPlayer();

就这么简单!现在音乐播放机处于idle状态,还没开始工作~


第二步:接好电源和音箱(设置监听事件)

播放机买回来了,得接好电源和音箱才能用。AVPlayer的监听事件就是"电源"和"音箱",帮咱们监听播放状态变化:

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

// 监听播放状态变化(电源指示灯)
音乐播放机.on('stateChange', async (state: string, reason: media.StateChangeReason) => {
  // 状态变化时的业务逻辑
  console.info(`播放状态变了:${state},原因:${reason}`);
});

// 监听错误信息(故障提示灯)
音乐播放机.on('error', (error: BusinessError) => {
  console.error(`播放出错了:${error.code}${error.message}`);
});

// 监听时长更新(唱片总时长)
音乐播放机.on('durationUpdate', (duration: number) => {
  console.info(`音乐总时长:${duration}`);
});

// 监听当前播放时间(进度条)
音乐播放机.on('timeUpdate', (time: number) => {
  console.info(`当前播放到:${time}`);
});

// 监听跳转完成(快进/快退完成)
音乐播放机.on('seekDone', (seekDoneTime: number) => {
  console.info(`跳转完成,现在播放到:${seekDoneTime}`);
});

// 监听倍速设置完成(调速完成)
音乐播放机.on('speedDone', (speed: number) => {
  console.info(`倍速设置完成:${speed}x`);
});

// 监听音量变化(音量调节完成)
音乐播放机.on('volumeChange', (vol: number) => {
  console.info(`音量调节完成:${vol}`);
});

// 监听缓冲进度(网络播放缓冲)
音乐播放机.on('bufferingUpdate', (infoType: media.BufferingInfoType, value: number) => {
  console.info(`缓冲信息:${infoType},进度:${value}%`);
});

// 监听音频焦点变化(防止被其他应用打断)
音乐播放机.on('audioInterrupt', (info: audio.InterruptEvent) => {
  console.info(`音频焦点变化:${JSON.stringify(info)}`);
});

🥦 西兰花小贴士

  • stateChangeerror是必须监听的事件,就像播放机必须接电源一样!
  • 这些监听必须在idle状态下、设置资源前完成,否则可能收不到状态变化~

第三步:放唱片(设置播放资源)

播放机接好线了,现在该放唱片了!设置AVPlayer的url属性就能指定要播放的音频资源:

// 设置播放资源(放唱片)
let 音乐地址 = 'https://example.com/music.mp3'; // 替换成你的音频地址

if (音乐播放机 == null) {
  return;
}

音乐播放机.url = 音乐地址;

设置完资源后,音乐播放机就进入了initialized状态,准备就绪!

🥦 关于资源路径的小知识

资源类型 注意事项
本地资源 必须用应用沙箱路径,参考获取应用文件路径
网络资源 必须申请ohos.permission.INTERNET权限
HAP资源 ResourceManager.getRawFd打开文件描述符
格式要求 必须是AVPlayer支持的播放格式

第四步:调整音效(设置音频渲染)

想让音乐更好听?可以调整音频渲染参数,就像调节播放机的音效:

import { audio } from '@kit.AudioKit';

// 设置音频渲染信息(调节音效)
音乐播放机.audioRendererInfo = {
  usage: audio.StreamUsage.STREAM_USAGE_MUSIC, // 流类型:音乐
  rendererFlags: 0                           // 渲染标志
};

🥦 西兰花小贴士

  • 流类型很重要!不同类型的音频要用不同的usage
    • 音乐:STREAM_USAGE_MUSIC
    • 电影:STREAM_USAGE_MOVIE
    • 通话:STREAM_USAGE_VOICE_COMMUNICATION
    • 参考选择合适的流类型了解更多

第五步:预热(准备播放)

唱片放好了,音效也调了,现在该预热播放机了!调用prepare()方法准备播放:

// 准备播放(预热播放机)
音乐播放机.prepare((err: BusinessError) => {
  if (err) {
    console.error('准备失败:' + err.message);
  } else {
    console.info('准备成功!可以开始播放了~');
    // 准备成功后可以获取时长、调整音量
  }
});

准备成功后,音乐播放机进入prepared状态,可以获取音乐时长并调整音量了!


第六步:开始播放(播控操作)

预热完成,终于可以开始播放音乐了!咱们来实现播放、暂停、跳转、停止等操作:

import { BusinessError } from '@kit.BasicServicesKit';

// 播放音乐
音乐播放机.play().then(() => {
  console.info('开始播放!');
}, (err: BusinessError) => {
  console.error('播放失败:' + err.message);
});

// 暂停播放
音乐播放机.pause((err: BusinessError) => {
  if (err) {
    console.error('暂停失败:' + err.message);
  } else {
    console.info('暂停播放');
  }
});

// 跳转播放(快进/快退)
let 目标时间: number = 10; // 跳转到10秒处
音乐播放机.seek(目标时间, media.SeekMode.SEEK_PREV_SYNC);

// 停止播放
音乐播放机.stop((err: BusinessError) => {
  if (err) {
    console.error('停止失败:' + err.message);
  } else {
    console.info('停止播放');
  }
});

就像操作家里的播放机一样简单!


第七步:换唱片(更换资源)

想听另一首歌?可以调用reset()方法重置播放机,然后更换资源:

// 重置播放机(取出唱片)
音乐播放机.reset((err: BusinessError) => {
  if (err) {
    console.error('重置失败:' + err.message);
  } else {
    console.info('重置成功!可以换唱片了~');
  }
});

// 更换资源(放新唱片)
let 新音乐地址 = 'https://example.com/new_music.mp3';
if (音乐播放机 == null) {
  return;
}
音乐播放机.url = 新音乐地址;

重置后,音乐播放机回到idle状态,可以重新设置资源并播放~


第八步:关机(释放资源)

听完音乐了,别忘了关机节约电量!调用release()方法释放AVPlayer资源:

// 释放资源(关机)
音乐播放机.release((err: BusinessError) => {
  if (err) {
    console.error('释放资源失败:' + err.message);
  } else {
    console.info('资源释放成功!');
  }
});

释放后,音乐播放机进入released状态,彻底退出播放流程~


🥦 完整示例:实现一个简单的音乐播放器

光说不练假把式,咱们来整个完整的示例!

1. 准备资源

先新建工程,下载示例工程,把以下资源复制到对应目录:

AVPlayerArkTSAudio
├── entry/src/main/ets/
│   └── pages/
│       └── Index.ets (播放界面)
└── entry/src/main/resources/
    ├── base/
    │   ├── element/
    │   │   ├── color.json
    │   │   ├── float.json
    │   │   └── string.json
    │   └── media/
    │       ├── ic_video_play.svg  (播放键)
    │       └── ic_video_pause.svg (暂停键)
    └── rawfile/
        └── test_01.mp3 (音频资源)

2. 实现播放界面

Index.ets中实现播放界面:

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

@Entry
@Component
struct Index {
  private 音乐播放机: media.AVPlayer | null = null;
  private isPlaying: boolean = false;
  private currentTime: number = 0;
  private duration: number = 0;
  private progress: number = 0;

  // 页面加载时初始化
  aboutToAppear() {
    this.initPlayer();
  }

  // 初始化播放器
  async initPlayer() {
    try {
      // 创建播放器实例
      this.音乐播放机 = await media.createAVPlayer();
      
      // 设置监听
      this.setPlayerListeners();
      
      // 设置本地资源
      let context = getContext(this) as common.UIAbilityContext;
      let resourceManager = context.resourceManager;
      let fd = await resourceManager.getRawFd('test_01.mp3');
      
      if (this.音乐播放机) {
        this.音乐播放机.fdSrc = {
          fd: fd.fd,
          offset: 0,
          length: fd.length
        };
        
        // 准备播放
        await this.preparePlayer();
      }
    } catch (error) {
      console.error('初始化播放器失败:' + JSON.stringify(error));
    }
  }

  // 设置播放器监听
  setPlayerListeners() {
    if (!this.音乐播放机) return;
    
    // 监听状态变化
    this.音乐播放机.on('stateChange', async (state: string) => {
      console.info(`状态变化:${state}`);
      if (state === 'playing') {
        this.isPlaying = true;
      } else if (state === 'paused' || state === 'completed') {
        this.isPlaying = false;
      }
    });
    
    // 监听错误
    this.音乐播放机.on('error', (error: BusinessError) => {
      console.error(`播放错误:${error.code}${error.message}`);
      promptAction.showToast({ message: '播放失败' });
    });
    
    // 监听时长更新
    this.音乐播放机.on('durationUpdate', (duration: number) => {
      this.duration = duration;
    });
    
    // 监听当前时间
    this.音乐播放机.on('timeUpdate', (time: number) => {
      this.currentTime = time;
      this.progress = this.duration > 0 ? (time / this.duration) * 100 : 0;
    });
  }

  // 准备播放器
  async preparePlayer() {
    if (!this.音乐播放机) return;
    
    return new Promise<void>((resolve, reject) => {
      this.音乐播放机?.prepare((err: BusinessError) => {
        if (err) {
          console.error('准备失败:' + err.message);
          reject(err);
        } else {
          console.info('准备成功');
          resolve();
        }
      });
    });
  }

  // 播放/暂停切换
  async togglePlay() {
    if (!this.音乐播放机) return;
    
    try {
      if (this.isPlaying) {
        await this.音乐播放机.pause();
      } else {
        await this.音乐播放机.play();
      }
    } catch (error) {
      console.error('播放/暂停失败:' + JSON.stringify(error));
    }
  }

  // 页面卸载时释放资源
  aboutToDisappear() {
    if (this.音乐播放机) {
      this.音乐播放机.release();
      this.音乐播放机 = null;
    }
  }

  build() {
    Column({ space: 20 }) {
      Text('简单音乐播放器')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .margin(20);
      
      // 进度条
      Slider({
        value: this.progress,
        min: 0,
        max: 100,
        style: SliderStyle.InSet
      })
        .width('80%')
        .onChange((value: number) => {
          // 拖动进度条跳转
          if (this.音乐播放机 && this.duration > 0) {
            let seekTime = (value / 100) * this.duration;
            this.音乐播放机.seek(seekTime, media.SeekMode.SEEK_PREV_SYNC);
          }
        });
      
      // 时间显示
      Row() {
        Text(this.formatTime(this.currentTime))
          .fontSize(14);
        Text('/')
          .fontSize(14);
        Text(this.formatTime(this.duration))
          .fontSize(14);
      }
      
      // 播放按钮
      Button({
        type: ButtonType.Circle,
        stateEffect: true
      }) {
        Image(this.isPlaying ? $r('app.media.ic_video_pause') : $r('app.media.ic_video_play'))
          .width(30)
          .height(30)
          .objectFit(ImageFit.Contain);
      }
      .width(80)
      .height(80)
      .backgroundColor('#1890ff')
      .onClick(() => {
        this.togglePlay();
      });
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center);
  }

  // 格式化时间
  formatTime(seconds: number): string {
    let min = Math.floor(seconds / 60);
    let sec = Math.floor(seconds % 60);
    return `${min.toString().padStart(2, '0')}:${sec.toString().padStart(2, '0')}`;
  }
}

3. 编译运行

编译工程并运行,就能看到一个简单的音乐播放器了!点击播放按钮就能播放rawfile里的音频~


总结一下

今天咱们学会了:

  1. AVPlayer的概念:就像音乐播放机,能播放各种音频格式
  2. 播放流程:从创建实例到释放资源的完整流程
  3. 监听事件:监听播放状态、错误、时长等信息
  4. 播控操作:实现播放、暂停、跳转、停止功能
  5. 资源管理:本地资源、网络资源、HAP资源的使用
  6. 完整示例:实现了一个简单的音乐播放器

是不是超简单?AVPlayer的使用其实就像操作家里的音乐播放机,跟着步骤来就能搞定~


📚 推荐资料

我是盐焗西兰花,
不教理论,只给你能跑的代码和避坑指南。
下期见!🥦

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐