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 ;
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 {
const fdObj = await resourceManager. getRawFd ( this . audioPath) ;
const avFileDescriptor: media. AVFileDescriptor = {
fd: fdObj. fd,
offset: fdObj. 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' :
this . avPlayer?. prepare ( ) ;
break ;
case 'prepared' :
console . log ( '准备完成' ) ;
break ;
case 'playing' :
this . isPlaying = true ;
this . startProgressTracking ( ) ;
break ;
case 'paused' :
this . isPlaying = false ;
this . stopProgressUpdate ( ) ;
break ;
case '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()返回值是否有效 offset 0 ≤ offset ≤ 文件大小-1 配合fs.stat验证偏移有效性 length 1 ≤ length ≤ 剩余字节数 动态计算:length = 文件大小 - offset
示例
播放HAP内嵌资源
typescriptconst fdObj = await resourceManager. getRawFd ( 'music.mp3' ) ;
const avFileDescriptor = {
fd: fdObj. fd,
offset: fdObj. offset,
length: fdObj. length
} ;
avPlayer. fdSrc = avFileDescriptor;
分段播放大型文件
typescript
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')
回调中处理文件访问异常(如权限不足或文件损坏)