鸿蒙-AVPlayer

news/2025/2/27 11:24:30
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; // 存储定时器ID
  private readonly audioPath: string = 'qingtian.mp3';

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

  //页面销毁
  aboutToDisappear(): void {
    this.releasePlayer();
  }

  // 修改为异步函数
  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('创建播放器失败: ' + JSON.stringify(err));
          return;
        }
        console.info('创建播放器success');
        this.avPlayer = player;
        this.setupPlayerEvents();
        this.avPlayer.fdSrc = avFileDescriptor;
      });
    } catch (error) {
      console.error('文件加载失败: ' + JSON.stringify(error));
    }
  }

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

    // 修改为字符串状态匹配
    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('准备完成');
          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('播放错误: ' + JSON.stringify(err));
      this.releasePlayer();
      this.initAudioPlayer();
    });
  }

  // 开始播放进度跟踪
  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);
  }

  // 停止进度更新
  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() {
      // 播放控制区域
      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('当前播放:' + this.audioPath.split('/').pop())
        .fontSize(16)
        .margin({ top: 20 })
    }
    .width('100%')
    .height('100%')
    .padding(20)
    .backgroundColor('#F5F5F5')
  }
}

media.AVFileDescriptor

fd:文件描述符

  • ​含义:操作系统分配的唯一标识符,代表已打开的文件句柄(file descriptor)。
  • ​作用:
    • 系统通过该标识符定位具体的媒体文件(如存储在rawfile目录下的音频文件或HAP包内嵌资源)
    • 用于跨进程文件访问时传递文件引用(如播放器服务与UI界面的数据交互)
    • 示例:通过resourceManager.getRawFd('music.mp3')获取打包资源文件的描述符

​offset:文件偏移量

  • ​含义:从文件起始位置到目标数据的字节偏移量(单位:字节)。
  • ​技术细节:
    • 当媒体文件被压缩或打包时(如HAP资源文件),需跳过文件头等非音频数据部分
    • 支持精确指定播放起始点(如从视频第10秒开始播放,需计算对应的字节偏移)
    • 示例:若资源文件在HAP包中的物理偏移为1024字节,则offset需设为1024

​length:数据长度

  • ​含义:需要读取的媒体数据总长度(单位:字节)。
  • ​关键作用:
    • 限制播放器读取范围,避免处理无关数据(如仅播放某段音频或视频片段)
    • 防止越界读取导致的崩溃(如文件实际大小小于声明长度时触发错误码5400102)
    • 示例:从HAP包中读取一个30秒的MP3片段时,需通过fs.statSync获取精确文件长度

参数关系与开发规范

参数典型取值范围异常处理建议
fd≥0(0表示无效句柄)检查fs.open()返回值是否有效
offset0 ≤ offset ≤ 文件大小-1配合fs.stat验证偏移有效性
length1 ≤ length ≤ 剩余字节数动态计算:length = 文件大小 - offset

示例

播放HAP内嵌资源
typescriptconst fdObj = await resourceManager.getRawFd('music.mp3');
const avFileDescriptor = {
  fd: fdObj.fd,
  offset: fdObj.offset, // 自动处理HAP打包偏移
  length: fdObj.length   // 精确获取资源实际长度
};
avPlayer.fdSrc = avFileDescriptor;  // 直接绑定播放源[3](@ref)
分段播放大型文件​
typescript// 播放视频第60-120秒的内容
const startOffset = 60 * bitrate;   // 根据码率计算字节偏移
const playLength = 60 * bitrate;
avPlayer.fdSrc = { fd, offset: startOffset, length: playLength };
开发注意事项:
  • offset + length超过实际文件大小,将触发BusinessError 5400102(参数非法)
  • 使用fs.close(fd)aboutToDisappear生命周期关闭文件描述符,避免资源泄漏
  • on('error')回调中处理文件访问异常(如权限不足或文件损坏)

http://www.niftyadmin.cn/n/5870034.html

相关文章

[记录贴] 火绒奇怪的进程保护

最近一次更新火绒6.0到最新版,发现processhacker的结束进程功能无法杀掉火绒的进程,弹窗提示如下: 可能是打开进程时做了权限过滤,火绒注册了两个回调函数如下: 但奇怪的是,在另外一台机器上面更新到最新版…

C++ Qt常见面试题(3):Qt内存管理机制

Qt 内存管理机制是其框架的重要组成部分,目的是简化开发者对内存的管理,减少内存泄漏的风险,同时提供高效的资源使用方式。Qt 的内存管理机制主要依赖于 对象树(Object Tree) 和 父子关系(Parent-Child Relationship) 的设计,通过智能管理对象的生命周期来实现自动化的…

ES如何打印DSL

看了一下官网版本已经来到了8.17 正常打印似乎不行,突破的地方则是 藏在JsonpDeserializable 这个注解上; JsonpDeserializable public class SearchRequest 因此只有反序列化出来之后才能打印,似乎就这么简单,看源码或许能更快…

【Mastering Vim 2_08】第七章:Vim 的个性化配置

【最新版《Mastering Vim》封面,涵盖 Vim 9.0 版特性】 文章目录 第七章 Vim 的个性化配置1 升级到最新版 pip2 定制配色方案2.1 配色方案 PaperColor 实战演练 3 美化并增强 Vim 状态栏4 gVim 的个性化定制5 实战:vimrc 配置文件的同步6 Vim 个性化定制…

国产编辑器EverEdit - 贴心的AES加密功能

1 AES 加密解密 1.1 应用场景 如果要在网络上传递一些敏感信息,为了防止信息泄露,对信息进行加密是一种比较好的方式,EverEdit提供了AES加密/解密功能。 1.2 使用方法 步骤1:在编辑器中选中要加密或解密文本。步骤2&#xff1a…

elementplus点击按钮直接预览图片

引用&#xff1a;https://blog.csdn.net/this_zq/article/details/134535539 <el-image-viewerv-if"showImagePreview":zoom-rate"1.2"close"closePreview":url-list"imgPreviewList"/>const showImagePreview ref(false) cons…

矩阵的奇异值(SVD)分解和线性变换

矩阵的奇异值&#xff08;SVD&#xff09;分解和线性变换 SVD定义 奇异值分解&#xff08;Singular Value Decomposition&#xff0c;简称 SVD&#xff09;是一种重要的线性代数工具&#xff0c;能够将任意矩阵 ( A ∈ R m n \mathbf{A} \in \mathbb{R}^{m \times n} A∈Rmn…

MAC 安装Tensorflow简单方法

MacOS M1 pro安装tensorflow 1、环境配置&#xff08;必需&#xff09; PYTHON版本&#xff1a;3.11.0&#xff0c;python采用homebrew安装。&#xff08;已有python或更高版本可跳过&#xff0c;更高版本未经过测试&#xff0c;题主自认为可行&#xff09; brew install pyt…