HarmonyOS 鸿蒙Next中如何使用avplayer实现边播边缓存能力
HarmonyOS 鸿蒙Next中如何使用avplayer实现边播边缓存能力 **求助:**在HarmonyOS单框架版本上开发应用,使用avplayer播放在线视频,怎么才能实现边缓存边播放的能力呢。
【问题分析】
AVPlayer自带边缓冲变播放的特性,整个流程涉及较多,且官方提供案例,楼主可以参考下面文档
【参考文档】
基于AVPlayer播放网络视频实践-基于AVPlayer播放视频系列实践-音频和视频-媒体 - 华为HarmonyOS开发者
更多关于HarmonyOS 鸿蒙Next中如何使用avplayer实现边播边缓存能力的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
可以通过设置avplayer的datasrc属性实现边播边缓存能力。
文档地址:https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-media-avplayer
将缓存下来数据写入到本地临时文件中, 然后读取临时文件中的数据播放,即可实现边播边缓存的能力。
import media from ‘@ohos.multimedia.media’; import common from ‘@ohos.app.ability.common’; import { BusinessError } from ‘@ohos.base’; import rcp from ‘@hms.collaboration.rcp’; import fs from ‘@ohos.file.fs’; import connection from ‘@ohos.net.connection’; // const DOWNLOAD_URL = ‘https://sns-video-bd.xhscdn.com/stream/110/258/01e602cadc11542d010370038e7ae8b418_258.mp4’; // const DOWNLOAD_URL = ‘https://media.w3.org/2010/05/sintel/trailer.mp4’; const DOWNLOAD_URL = ‘https://ss0.bdstatic.com/-0U0bnSm1A5BphGlnYG/cae-legoup-video-target/93be3d88-9fc2-4fbd-bd14-833bca731ca7.mp4’; // const DOWNLOAD_URL = ‘https://vjs.zencdn.net/v/oceans.mp4’; const request = new rcp.Request(DOWNLOAD_URL); const session = rcp.createSession(); let context = getContext(this) as common.UIAbilityContext; let pathDir = context.filesDir; let path = pathDir + ‘/1234.mp4’; let bufferingStart = false; if (fs.accessSync(path)) { fs.unlinkSync(path); } const fd = fs.openSync(path, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE).fd; const readFd = fs.openSync(path, fs.OpenMode.READ_ONLY).fd; function GetDecodedBuffer(buffer: ArrayBuffer): ArrayBuffer { return buffer; } let g_from = 0; let g_to = 0; let recieve = 0; let last_recieve = 0; request.configuration = { tracing: { httpEventsHandler: { onDataReceive: (incomingData: ArrayBuffer) => { const writeLength = fs.writeSync(fd, GetDecodedBuffer(incomingData)); recieve+= writeLength; console.info('MineRcp recieve Length = ’ + writeLength.toString() + " , recieve = " + recieve); return incomingData.byteLength; } } } };
function download(length: number) { console.info('MineRcp download from = ’ + g_from + 'to = ’ + (g_from + length - 1)); request.transferRange = { from: g_from, to: g_from + length - 1, }; g_to = g_from + length - 1; g_from += length; session.fetch(request); } @Entry @Component struct AVPlayerDemo { xComponentController: XComponentController = new XComponentController(); surfaceID: string = ‘’; public xComponentContext: Record<string, () => void> = {} public player: media.AVPlayer | null = null; private fileSize: number = -1; private fd: number = 0; public isSeek: boolean = true; @State isBuffering: Visibility = Visibility.None; build() { Column() { Stack() { XComponent({ id: ‘AVPlayer’, type: XComponentType.SURFACE, controller: this.xComponentController }) .onLoad(() => { this.surfaceID = this.xComponentController.getXComponentSurfaceId() this.xComponentContext = this.xComponentController.getXComponentContext() as Record<string, () => void> }) Text(‘缓冲中。。。’) .visibility(this.isBuffering) .fontSize(20) .width(300) .textAlign(TextAlign.Center) .fontColor(Color.White) } Button(‘speed’) .onClick(() => { this.player?.setSpeed(media.PlaybackSpeed.SPEED_FORWARD_2_00_X); }) .margin({ right: 12 }) .margin({ top: 10 }) .width(150) Button(‘paused’) .onClick(() => { this.player?.pause(); }) .margin({ right: 12 }) .margin({ top: 20 }) .width(150) Button(‘seek’) .onClick(() => { this.player?.seek(9000, 0); }) .margin({ right: 12 }) .margin({ top: 20 }) .width(150) Button(‘player’) .onClick(() => { this.avPlayerDataSrcNOSeekDemo() }) .margin({ right: 12 }) .margin({ top: 20 }) .width(150) Button(‘reset’) .onClick(() => { this.player?.reset(); }) .margin({ right: 12 }) .margin({ top: 20 }) .width(150) } .margin({ top: 40 }) .height(400) } async avPlayerDataSrcNOSeekDemo() { download(1024 * 1024); // 获取播放文件的大小 const headResp = await session.head(DOWNLOAD_URL); this.fileSize = Number(headResp.headers[‘content-length’]); console.info('MineRcp fileSize = ’ + this.fileSize.toString()); // 创建avPlayer实例对象 this.player = await media.createAVPlayer(); // 创建状态机变化回调函数 this.setAVPlayerCallback(this.player);
// dataSrc播放模式的的播放源地址,当播放为Seek模式时fileSize为播放文件的具体大小,下面会对fileSize赋值
let src: media.AVDataSrcDescriptor = {
fileSize: -1,
callback: (buf: ArrayBuffer, length: number, pos?: number) => {
let num = 0;
if (buf == undefined || length == undefined) {
return -1;
}
num = fs.readSync(readFd, buf);
console.info('MineRcp cacheBuffer after checkBuffer Length = ' + num.toString() + ', pos: ' + pos + ', g_to: ' + g_to);
if (num > 0) {
if (pos != undefined && pos < g_to && g_to - pos < 100 * 1024) {
console.info('MineRcp data not enough, download more');
let downloadLength = 1024 * 1024;
if (this.fileSize - g_from <= downloadLength) {
downloadLength = this.fileSize - g_from;
}
download(downloadLength);
}
bufferingStart = false;
return num;
}
if (num == 0) {
console.info('MineRcp no data read, download more');
if (!bufferingStart) {
bufferingStart = true;
let downloadLength = 1024 * 1024;
if (this.fileSize - g_from <= downloadLength) {
downloadLength = this.fileSize - g_from;
}
download(downloadLength);
}
return 0;
}
return -1;
}
}
src.fileSize = this.fileSize;
this.isSeek = false; // 支持seek操作
this.player.dataSrc = src;
} // 注册avplayer回调函数 private setAVPlayerCallback(avPlayer: media.AVPlayer) { if (this.player === null) { return; } // // 响应API调用,监听seek()请求完成情况。 avPlayer.on(‘speedDone’, (number: Number) => this.speedDone(number)); avPlayer.on(‘seekDone’, (number: Number) => this.seekDone(number)); avPlayer.on(‘bufferingUpdate’, (infoType: media.BufferingInfoType, value: number) => { if (infoType == media.BufferingInfoType.BUFFERING_START) { this.isBuffering = Visibility.Visible; } if (infoType == media.BufferingInfoType.BUFFERING_END) { this.isBuffering = Visibility.None; } }); // 状态机变化回调函数 avPlayer.on(‘stateChange’, async (state: string, reason: media.StateChangeReason) => { switch (state) { case ‘idle’: // 成功调用reset接口后触发该状态机上报 console.info(‘AVPlayer state idle called.’); this.avPlayerDataSrcNOSeekDemo() //avPlayer.release(); // 调用release接口销毁实例对象 break; case ‘initialized’: // avplayer 设置播放源后触发该状态上报 console.info(‘AVPlayer state initialized called.’); avPlayer.surfaceId = this.surfaceID; // 设置显示画面,当播放的资源为纯音频时无需设置 console.info(‘prepare start.’); avPlayer.prepare(); console.info(‘prepare end.’); break; case ‘prepared’: // prepare调用成功后上报该状态机 console.info(‘AVPlayer state prepared called.’); avPlayer.play(); // 调用播放接口开始播放 break; case ‘playing’: // play成功调用后触发该状态机上报 console.info(‘AVPlayer state playing called.’); break; case ‘paused’: // pause成功调用后触发该状态机上报 console.info(‘AVPlayer state paused called.’); break; case ‘completed’: // 播放结束后触发该状态机上报 console.info(‘AVPlayer state completed called.’); avPlayer.stop(); //调用播放结束接口 break; case ‘stopped’: // stop接口成功调用后触发该状态机上报 console.info(‘AVPlayer state stopped called.’); avPlayer.release(); // 调用reset接口初始化avplayer状态 break; case ‘released’: console.info(‘AVPlayer state released called.’); fs.unlinkSync(path); session.cancel(request); break; default: console.info(‘AVPlayer state unknown called.’); break; } }) }
private speedDone(number: Number) { console.info(‘AVPlayer ## speedDone’); } // private seekDone(number: Number) { console.info(‘AVPlayer ## speedDone’); } }
补充一下实践Demo和原理文档
开发者你好,可以参考一下
在HarmonyOS Next中,使用AVPlayer实现边播边缓存,需结合AVFileDescriptor和预下载机制。通过AVSession设置播放源时,可指定本地文件描述符。缓存实现通常需先通过网络模块(如http)将媒体数据预下载至本地文件,生成AVFileDescriptor后传递给AVPlayer进行播放。此过程需自行管理缓存文件的下载、存储与生命周期,AVPlayer核心负责解码与渲染。
在HarmonyOS Next中,使用AVPlayer实现边播边缓存的核心是结合AVFileDescriptor与本地文件管理。以下是关键实现步骤:
-
网络下载与本地写入: 使用
@ohos.net.http模块下载视频数据,同时通过@ohos.file.fs将数据流实时写入本地文件(如应用沙箱路径)。建议采用分片下载策略以平衡内存与缓存效率。 -
AVPlayer播放本地文件: 创建AVPlayer实例后,通过
fdSrc方式设置数据源:let fd = fs.openSync(localFilePath, fs.OpenMode.READ_ONLY); avPlayer.fdSrc({ fd: fd.fd, offset: 0, length: fileSize });播放器会从已下载的本地文件部分读取数据,同时后台持续下载后续内容写入文件。
-
进度同步与跳转处理:
- 监听下载进度与播放进度,确保播放指针不超过已缓存的数据范围。
- 若用户跳转到未缓存位置,需暂停播放并等待该位置数据下载完成后再恢复。
-
缓存管理: 可设计LRU机制管理缓存文件,避免存储空间过度占用。注意使用
fs.truncate或分片文件方式便于清理。
注意事项:
- 需申请
ohos.permission.INTERNET和ohos.permission.MEDIA_LOCATION权限。 - 在线视频需支持范围请求(HTTP Range Header),否则无法实现精准缓存。
- 播放过程中需处理网络中断、文件读写异常等场景,确保体验连贯。
此方案通过将网络流转化为本地文件访问,间接实现边播边存,是目前AVPlayer支持的标准方法。

