鴻蒙-AVPlayer

compileVersion 5.0.2(14)

音頻播放

import media from '@ohos.multimedia.media';
import common from '@ohos.app.ability.common';
import { BusinessError } from '@ohos.base';

@Entry
@Component
struct AudioPlayer {
  private avPlayer: media.AVPlayer | null = null;
  @State isPlaying: boolean = false;
  @State playProgress: number = 0;
  private timerId: number | null = null; // 存儲(chǔ)定時(shí)器ID
  private readonly audioPath: string = 'qingtian.mp3';

  //頁面初始化
  aboutToAppear() {
    this.initAudioPlayer();
  }

  //頁面銷毀
  aboutToDisappear(): void {
    this.releasePlayer();
  }

  // 修改為異步函數(shù)
  private async initAudioPlayer() {
    console.log('initAudioPlayer=====');
    const context = getContext(this) as common.UIAbilityContext;
    const resourceManager = context.resourceManager;

    try {
      // 添加await處理Promise
      const fdObj = await resourceManager.getRawFd(this.audioPath);
      const avFileDescriptor: media.AVFileDescriptor = {
        fd: fdObj.fd,
        offset: fdObj.offset, // 已正確處理offset屬性
        length: fdObj.length
      };

      media.createAVPlayer((err: BusinessError, player: media.AVPlayer) => {
        if (err) {
          console.error('創(chuàng)建播放器失敗: ' + JSON.stringify(err));
          return;
        }
        console.info('創(chuàng)建播放器success');
        this.avPlayer = player;
        this.setupPlayerEvents();
        this.avPlayer.fdSrc = avFileDescriptor;
      });
    } catch (error) {
      console.error('文件加載失敗: ' + JSON.stringify(error));
    }
  }

  private setupPlayerEvents() {
    if (!this.avPlayer) {
      return;
    }

    // 修改為字符串狀態(tài)匹配
    this.avPlayer.on('stateChange', (state: string) => {
      console.log('stateChange:' + state);
      switch (state) {
        case 'initialized': // 原media.AVPlayerState.PREPARED
          this.avPlayer?.prepare();
          break;
        case 'prepared': // 原media.AVPlayerState.PREPARED
          console.log('準(zhǔn)備完成');
          break;
        case 'playing': // 原media.AVPlayerState.PLAYING
          this.isPlaying = true;
          this.startProgressTracking();
          break;
        case 'paused': // 原media.AVPlayerState.PAUSED
          this.isPlaying = false;
          this.stopProgressUpdate();
          break;
        case 'completed': // 原media.AVPlayerState.COMPLETED
          this.isPlaying = false;
          this.playProgress = 100;
          this.stopProgressUpdate();
          break;
      }
    });

    this.avPlayer.on('error', (err: BusinessError) => {
      console.error('播放錯(cuò)誤: ' + JSON.stringify(err));
      this.releasePlayer();
      this.initAudioPlayer();
    });
  }

  // 開始播放進(jìn)度跟蹤
  private startProgressTracking() {
    console.log('startProgressTracking=====');
    this.timerId = setInterval(() => {
      if (this.avPlayer && this.avPlayer.duration > 0) {
        console.log('setInterval currentTime=' + this.avPlayer.currentTime + ' duration=' + this.avPlayer.duration);
        this.playProgress = (this.avPlayer.currentTime / this.avPlayer.duration) * 100;
      }
    }, 1000);
    console.log('this.timerId=' + this.timerId);
  }

  // 停止進(jìn)度更新
  private stopProgressUpdate() {
    console.log('stopProgressUpdate=====');
    if (this.timerId !== null) {
      clearInterval(this.timerId);
      this.timerId = null;
    }
  }

  // 釋放播放器資源
  private releasePlayer() {
    console.log('releasePlayer=====');
    if (this.avPlayer) {
      this.avPlayer.release();
      this.avPlayer = null;
    }
  }

  // 播放/暫??刂?  private togglePlayback() {
    if (!this.avPlayer) {
      return;
    }
    if (this.isPlaying) {
      this.avPlayer.pause();
    } else {
      if (this.avPlayer.currentTime >= this.avPlayer.duration) {
        this.avPlayer.seek(0);
      }
      this.avPlayer.play();
    }
  }

  build() {
    Column() {
      // 播放控制區(qū)域
      Row({ space: 20 }) {
        Button(this.isPlaying ? '暫停' : '播放')
          .onClick(() => this.togglePlayback())
          .width(100)
          .height(40)

        Progress({ value: this.playProgress, total: 100 })
          .width('60%')
          .height(10)
          .color('#409EFF')
      }
      .padding(20)
      .width('100%')

      // 音頻信息顯示
      Text('當(dāng)前播放:' + this.audioPath.split('/').pop())
        .fontSize(16)
        .margin({ top: 20 })
    }
    .width('100%')
    .height('100%')
    .padding(20)
    .backgroundColor('#F5F5F5')
  }
}

media.AVFileDescriptor

fd:文件描述符

  • 含義:操作系統(tǒng)分配的唯一標(biāo)識(shí)符,代表已打開的文件句柄(file descriptor)。
  • 作用:
    • 系統(tǒng)通過該標(biāo)識(shí)符定位具體的媒體文件(如存儲(chǔ)在rawfile目錄下的音頻文件或HAP包內(nèi)嵌資源)
    • 用于跨進(jìn)程文件訪問時(shí)傳遞文件引用(如播放器服務(wù)與UI界面的數(shù)據(jù)交互)
    • 示例:通過resourceManager.getRawFd('music.mp3')獲取打包資源文件的描述符

offset:文件偏移量

  • 含義:從文件起始位置到目標(biāo)數(shù)據(jù)的字節(jié)偏移量(單位:字節(jié))。
  • 技術(shù)細(xì)節(jié):
    • 當(dāng)媒體文件被壓縮或打包時(shí)(如HAP資源文件),需跳過文件頭等非音頻數(shù)據(jù)部分
    • 支持精確指定播放起始點(diǎn)(如從視頻第10秒開始播放,需計(jì)算對(duì)應(yīng)的字節(jié)偏移)
    • 示例:若資源文件在HAP包中的物理偏移為1024字節(jié),則offset需設(shè)為1024

length:數(shù)據(jù)長(zhǎng)度

  • 含義:需要讀取的媒體數(shù)據(jù)總長(zhǎng)度(單位:字節(jié))。
  • 關(guān)鍵作用:
    • 限制播放器讀取范圍,避免處理無關(guān)數(shù)據(jù)(如僅播放某段音頻或視頻片段)
    • 防止越界讀取導(dǎo)致的崩潰(如文件實(shí)際大小小于聲明長(zhǎng)度時(shí)觸發(fā)錯(cuò)誤碼5400102)
    • 示例:從HAP包中讀取一個(gè)30秒的MP3片段時(shí),需通過fs.statSync獲取精確文件長(zhǎng)度

參數(shù)關(guān)系與開發(fā)規(guī)范

參數(shù) 典型取值范圍 異常處理建議
fd ≥0(0表示無效句柄) 檢查fs.open()返回值是否有效
offset 0 ≤ offset ≤ 文件大小-1 配合fs.stat驗(yàn)證偏移有效性
length 1 ≤ length ≤ 剩余字節(jié)數(shù) 動(dòng)態(tài)計(jì)算:length = 文件大小 - offset

示例

播放HAP內(nèi)嵌資源

typescriptconst fdObj = await resourceManager.getRawFd('music.mp3');
const avFileDescriptor = {
  fd: fdObj.fd,
  offset: fdObj.offset, // 自動(dòng)處理HAP打包偏移
  length: fdObj.length   // 精確獲取資源實(shí)際長(zhǎng)度
};
avPlayer.fdSrc = avFileDescriptor;  // 直接綁定播放源[3](@ref)

分段播放大型文件

typescript// 播放視頻第60-120秒的內(nèi)容
const startOffset = 60 * bitrate;   // 根據(jù)碼率計(jì)算字節(jié)偏移
const playLength = 60 * bitrate;
avPlayer.fdSrc = { fd, offset: startOffset, length: playLength };

開發(fā)注意事項(xiàng):

  • offset + length超過實(shí)際文件大小,將觸發(fā)BusinessError 5400102(參數(shù)非法)
  • 使用fs.close(fd)aboutToDisappear生命周期關(guān)閉文件描述符,避免資源泄漏
  • on('error')回調(diào)中處理文件訪問異常(如權(quán)限不足或文件損壞)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 概要 64學(xué)時(shí) 3.5學(xué)分 章節(jié)安排 電子商務(wù)網(wǎng)站概況 HTML5+CSS3 JavaScript Node 電子...
    阿啊阿吖丁閱讀 9,793評(píng)論 0 3
  • 簡(jiǎn)介 用簡(jiǎn)單的話來定義tcpdump,就是:dump the traffic on a network,根據(jù)使用者...
    JasonShi6306421閱讀 1,334評(píng)論 0 1
  • 簡(jiǎn)介 用簡(jiǎn)單的話來定義tcpdump,就是:dump the traffic on a network,根據(jù)使用者...
    保川閱讀 6,061評(píng)論 1 13
  • 簡(jiǎn)介 用簡(jiǎn)單的話來定義tcpdump,就是:dump the traffic on a network,根據(jù)使用者...
    中科恒信閱讀 600評(píng)論 0 2
  • 簡(jiǎn)介 用簡(jiǎn)單的話來定義tcpdump,就是:dump the traffic on a network,根據(jù)使用者...
    xiaomonkeyhou閱讀 402評(píng)論 0 0

友情鏈接更多精彩內(nèi)容