HarmonyOS 媒体播放开发实战(音频播放)

2025-06-26 13:08:33
114次阅读
0个评论

HarmonyOS 媒体播放开发实战

做HarmonyOS开发时,媒体播放功能算是比较常用的需求了。不管是音乐播放器、视频应用还是短视频功能,都离不开AVPlayer这个核心组件。说实话,刚开始接触AVPlayer的时候还挺头疼的,状态管理、生命周期、错误处理这些都需要仔细处理,稍不注意就容易出问题。

AVPlayer是HarmonyOS提供的音视频播放引擎,支持多种格式的音频和视频文件。相比之前的Media API,AVPlayer的功能更强大,使用也更灵活。但是灵活的代价就是复杂度增加了,需要开发者对播放器的状态机制有比较深入的理解。

基础使用流程

AVPlayer的使用遵循严格的状态机制,主要包括idle、initialized、prepared、playing、paused、stopped、released等状态。每个状态都有其对应的可执行操作,违反状态规则就会报错。我刚开始就因为在错误的状态下调用方法而频繁崩溃。

最基本的播放流程是:创建AVPlayer实例 → 设置资源URL → 准备播放 → 开始播放。看起来简单,但每一步都有需要注意的细节。比如设置URL后必须等待prepared状态才能调用play方法,否则会失败。

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

@Entry
@Component
struct MediaPlayerPage {
  private avPlayer: media.AVPlayer | null = null
  @State isPlaying: boolean = false
  @State currentTime: number = 0
  @State duration: number = 0
  @State playerState: string = 'idle'

  async aboutToAppear() {
    await this.initPlayer()
  }

  aboutToDisappear() {
    this.releasePlayer()
  }

  private async initPlayer() {
    try {
      // 创建AVPlayer实例
      this.avPlayer = await media.createAVPlayer()
      
      // 监听状态变化
      this.avPlayer.on('stateChange', (state: string) => {
        console.info(`播放器状态变化: ${state}`)
        this.playerState = state
        
        switch (state) {
          case 'prepared':
            // 准备完成,可以获取时长信息
            this.duration = this.avPlayer?.duration || 0
            break
          case 'playing':
            this.isPlaying = true
            break
          case 'paused':
          case 'stopped':
            this.isPlaying = false
            break
        }
      })

      // 监听播放进度
      this.avPlayer.on('timeUpdate', (time: number) => {
        this.currentTime = time
      })

      // 监听播放完成
      this.avPlayer.on('endOfStream', () => {
        console.info('播放完成')
        this.isPlaying = false
        this.currentTime = 0
      })

      // 监听错误
      this.avPlayer.on('error', (error: BusinessError) => {
        console.error(`播放器错误: ${error.message}`)
        this.isPlaying = false
      })

    } catch (error) {
      console.error('初始化播放器失败:', error)
    }
  }

  private async loadMedia(url: string) {
    if (!this.avPlayer) {
      console.error('播放器未初始化')
      return
    }

    try {
      // 设置媒体资源
      this.avPlayer.url = url
      
      // 等待prepared状态
      // 注意:这里不需要手动调用prepare,设置url后会自动准备
      
    } catch (error) {
      console.error('加载媒体失败:', error)
    }
  }

  private async playOrPause() {
    if (!this.avPlayer) return

    try {
      if (this.isPlaying) {
        await this.avPlayer.pause()
      } else {
        await this.avPlayer.play()
      }
    } catch (error) {
      console.error('播放/暂停失败:', error)
    }
  }

  private async seekTo(time: number) {
    if (!this.avPlayer) return

    try {
      await this.avPlayer.seek(time)
    } catch (error) {
      console.error('跳转失败:', error)
    }
  }

  private releasePlayer() {
    if (this.avPlayer) {
      this.avPlayer.release()
      this.avPlayer = null
    }
  }

  build() {
    Column({ space: 20 }) {
      Text(`播放器状态: ${this.playerState}`)
        .fontSize(16)

      // 播放进度条
      Row() {
        Text(this.formatTime(this.currentTime))
          .fontSize(12)
        
        Slider({
          value: this.currentTime,
          min: 0,
          max: this.duration,
          style: SliderStyle.OutSet
        })
          .layoutWeight(1)
          .onChange((value: number) => {
            this.seekTo(value * 1000) // 转换为毫秒
          })
        
        Text(this.formatTime(this.duration))
          .fontSize(12)
      }
      .width('100%')

      // 控制按钮
      Row({ space: 20 }) {
        Button('加载音频')
          .onClick(() => {
            this.loadMedia('xxxxxx')
          })

        Button(this.isPlaying ? '暂停' : '播放')
          .onClick(() => {
            this.playOrPause()
          })
          .enabled(this.playerState === 'prepared' || 
                   this.playerState === 'playing' || 
                   this.playerState === 'paused')

        Button('停止')
          .onClick(async () => {
            if (this.avPlayer) {
              await this.avPlayer.stop()
            }
          })
      }
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .padding(20)
  }

  private formatTime(timeMs: number): string {
    const seconds = Math.floor(timeMs / 1000)
    const mins = Math.floor(seconds / 60)
    const secs = seconds % 60
    return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
  }
}

常见问题和解决方案

在实际开发中,我遇到过不少坑,这里分享几个比较典型的:

状态管理混乱:最常见的问题就是在错误的状态下调用方法。我的建议是在每个操作前都检查当前状态,确保操作的合法性。可以写个状态检查的工具函数,避免重复代码。

内存泄漏:AVPlayer如果不及时释放,很容易造成内存泄漏。特别是在页面跳转时,一定要记得调用release方法。我现在的习惯是在aboutToDisappear生命周期中统一清理资源。

网络媒体加载慢:对于网络媒体资源,加载时间可能比较长。用户体验方面,建议添加loading状态和进度提示。另外可以考虑预加载机制,提前准备下一首歌曲。

错误处理不完善:网络异常、格式不支持、权限问题等都可能导致播放失败。一定要监听error事件,并给用户友好的错误提示。我通常会根据错误类型显示不同的提示信息。

性能优化建议

AVPlayer的性能优化主要从几个方面考虑:

资源管理:不要创建过多的AVPlayer实例,一般一个应用有一个全局的播放器实例就够了。如果确实需要多个,记得及时释放不用的实例。

缓存策略:对于经常播放的音频,可以考虑本地缓存。HarmonyOS提供了相关的缓存API,合理使用能显著提升用户体验。

后台播放:如果需要后台播放功能,需要申请相应的权限,并正确处理应用生命周期。注意在应用进入后台时不要停止播放器,而是让它继续运行。

总的来说,AVPlayer功能强大但使用复杂,需要仔细处理各种状态和异常情况。刚开始可能会觉得繁琐,但熟练后就能体会到它的灵活性和强大功能了。关键是要多实践,多踩坑,积累经验。希望这些分享对大家有帮助!

收藏00

登录 后评论。没有帐号? 注册 一个。