刷视频刷到停不下来,结果自己 App 播放还磕磕绊绊?
本文介绍了鸿蒙系统多媒体开发中音视频播放的实现方法。文章首先指出用户对播放体验的高要求,并梳理了鸿蒙多媒体API的主要模块,包括媒体播放内核、UI组件层和会话控制层。重点讲解了VideoPlayer组件的使用方式,从基础播放到事件监听与控制逻辑的实现,同时介绍了流媒体播放的注意事项和状态管理。文章强调开发者需要建立完整的播放状态模型,以提供流畅的用户体验。
大家好,我是[晚风依旧似温柔],新人一枚,欢迎大家关注~
本文目录:
前言
——鸿蒙多媒体(音视频播放)你真该系统搞一次
说句心里话,现在的用户对“播放体验”几乎是零容忍:
- 视频一开头就转圈圈?关。
- 切小窗一卡一卡?卸。
- 音频后台一会儿就断?差评。
而对我们这种写代码的来说,鸿蒙多媒体这块表面看就两件事:音频播放、视频播放;真开搞的时候才发现:
- API 一堆:
media,avSession,VideoPlayer组件、流式播放、事件监听…… - 既要管“能不能播”,还要管“播得顺不顺、清不清、切不切全屏、小窗”。
这篇就按你给的大纲来,一次性把 鸿蒙媒体能力开发:音频 & 视频播放 的主线走一遍,尽量写成那种:
你下次再写播放器页面,脑子里已经有一整套结构,而不是边写边百度。
一、多媒体 API 全景:先认清“有什么”,再决定“用哪个”
鸿蒙多媒体相关的能力,大致可以分成几块(实际模块名字随版本略有调整,但角色很固定):
-
媒体播放内核:
- 音频播放器:
media.createAudioPlayer()/AudioPlayer - 视频播放器:
media.createAVPlayer()/AVPlayer
- 音频播放器:
-
UI 组件层:
VideoPlayerArkUI 组件:帮你把 UI + 播放整合在一起
-
会话控制层(可选):
avSession:控制通知栏播放、耳机控制、系统媒体控制(稍复杂,这里不过多展开)
-
解码 / 渲染相关:由系统内核处理,我们更多是调参数、喂 URL 或文件
可以简单理解成两条路:
-
纯逻辑播放器(AudioPlayer/AVPlayer)
- 你自己处理 UI、进度条、按钮等
-
VideoPlayer UI 组件 + 播放器封装
- 视频播放场景用这个更轻松
这篇我们重点放在:
- 音频:用 Audio 播放器 + 自定义 UI
- 视频:靠
VideoPlayer+ 必要的控制逻辑
二、VideoPlayer 组件:快速做出一个像样的视频界面
说实话,如果你只是想做个普通的视频播放页,不搞花式弹幕、滤镜、花里胡哨的进度条,那么用 ArkUI 自带的 VideoPlayer 几乎是性价比最高的选择。
2.1 最小可运行示例:本地或网络视频一键播放
import media from '@ohos.multimedia.media';
@Entry
@Component
struct SimpleVideoPage {
// 媒体 URL:可以是本地 file uri,也可以是 http(s) 流
@State videoSrc: string = 'https://example.com/demo.mp4';
build() {
Column() {
VideoPlayer({
src: this.videoSrc,
controls: true // 显示系统默认的控制条
})
.width('100%')
.height(240)
}
.width('100%')
.height('100%')
.backgroundColor('#000000')
}
}
这就已经是一个“能用”的播放器了:
- 自带播放/暂停按钮
- 能拖动进度
- 支持全屏切换(部分版本自带,全屏逻辑也可以自己包)
但只停在这个层级就有点太 Demo 了,我们肯定要更细粒度控制——比如事件监听、播放状态管理、自定义控制条等。
2.2 控制与事件:别只会 .src,得会“听它说话”
真实业务里,通常你需要:
- 知道当前播放状态(播放中 / 暂停 / 结束 / 出错)
- 拿到当前进度 / 总时长
- 响应用户操作(点击自定义按钮触发播放、暂停、跳转等)
通常做法是结合 控制器 + 事件回调,比如:
import media from '@ohos.multimedia.media';
import { VideoPlayerController } from '@ohos.arkui.ui'; // 示例,具体视版本而定
@Entry
@Component
struct VideoWithEvents {
private controller: VideoPlayerController = new VideoPlayerController();
@State src: string = 'https://example.com/demo.mp4';
@State duration: number = 0;
@State position: number = 0;
@State isPlaying: boolean = false;
aboutToAppear() {
// 可以在这里绑定 controller 的事件(部分版本支持)
}
build() {
Column() {
VideoPlayer({
src: this.src,
controller: this.controller
})
.width('100%')
.height(220)
.onPrepared((info) => {
// 媒体准备好,可以获得 duration
this.duration = info?.duration ?? 0;
})
.onPlay(() => {
this.isPlaying = true;
})
.onPause(() => {
this.isPlaying = false;
})
.onFinish(() => {
this.isPlaying = false;
this.position = this.duration;
})
.onError((err) => {
console.error('video error: ', JSON.stringify(err));
})
// 自定义控制条
Row() {
Button(this.isPlaying ? '暂停' : '播放')
.onClick(() => {
if (this.isPlaying) {
this.controller.pause();
} else {
this.controller.play();
}
})
Slider({
value: this.position,
min: 0,
max: this.duration
})
.onChange((value) => {
this.position = value;
})
.onChangeEnd((value) => {
this.controller.seekTo(value);
})
.margin({ left: 16 })
}
.margin({ top: 12 })
}
}
}
核心思路:
- 播放状态 →
@State(isPlaying,position,duration)- 播放行为 →
controller(play/pause/seekTo)- UI 按钮 & 进度条只是“操作者”和“显示器”
三、流媒体播放:点播、直播、M3U8,这些咋搞?
音视频领域,“流媒体”是绕不开的关键词:
- 点播:比如
https://xxx.com/video/haha.m3u8 - 直播:RTMP / HLS / DASH 等协议,前端通常是 HLS 地址
鸿蒙播放层对“URL 是什么协议”这一块,基本做的是 透明支持:你只要给出系统支持的 URL,就按普通视频流去播。
3.1 流媒体与本地媒体,对播放器来说区别不大
对 VideoPlayer/AVPlayer 来说,只要底层 codec & protocol 支持,它就可以:
@State src: string = 'https://example.com/live/stream.m3u8';
VideoPlayer({ src: this.src, ... })
但业务上,你得考虑:
- 流媒体可能没有固定时长(直播)
- 网络抖动会引起缓冲 / 卡顿,需要给出友好提示
- 可能需要手动做“重试 & 重新连接”
3.2 流媒体播放状态:重点关注缓冲 & 错误
对于直播 / HLS 这类,建议重点关注:
onBufferedUpdate:缓冲进度(不同版本名字不同,大致含义一致)onError:网络中断、流断开及时重试onInfo:有些播放器会抛出缓冲开始 / 结束信息
伪代码示意:
VideoPlayer({ src: this.src, controller: this.controller })
.onInfo((info) => {
if (info.type === 'BUFFERING_START') {
this.isBuffering = true;
} else if (info.type === 'BUFFERING_END') {
this.isBuffering = false;
}
})
.onError((err) => {
this.isBuffering = false;
this.errorMsg = '网络异常,正在尝试重连...';
this.retryPlay();
})
我们不一定能拿到所有底层细节,但应该在 UI 上至少做到:
- 卡顿时有 Loading 提示
- 失败时有重试按钮 & 自动退避重试机制
四、播放控制与事件监听:播放器不是“黑盒”
无论是组件式的 VideoPlayer 还是逻辑层的 AudioPlayer/AVPlayer,你都需要用一套统一的“状态模型”包起来,否则业务逻辑会很散。
4.1 播放状态模型(建议你自己定义一个)
哪怕官方没帮你定义完整枚举,你也可以 在业务层做一份“抽象状态”,比如:
enum PlayerState {
IDLE = 'idle', // 未设置源
PREPARING = 'preparing',
PLAYING = 'playing',
PAUSED = 'paused',
COMPLETED = 'completed',
ERROR = 'error'
}
然后封装一个简单的播放器管理类:
class VideoPlayerModel {
state: PlayerState = PlayerState.IDLE;
duration: number = 0;
position: number = 0;
error: string = '';
attachController(ctrl: VideoPlayerController) {
ctrl.onPrepared = (info) => {
this.duration = info?.duration ?? 0;
this.state = PlayerState.PAUSED; // 准备好但未播放
};
ctrl.onPlay = () => this.state = PlayerState.PLAYING;
ctrl.onPause = () => this.state = PlayerState.PAUSED;
ctrl.onFinish = () => this.state = PlayerState.COMPLETED;
ctrl.onError = (err) => {
this.state = PlayerState.ERROR;
this.error = JSON.stringify(err);
};
}
}
UI 层就只关心 model.state、model.duration、model.position,减少直接到处 controller.onXXX 绑事件的混乱感。
4.2 音频播放:列表播放器 + 后台播放的基本套路
音频场景一般会有几个要素:
- 播放列表(本地 / 在线)
- 当前播放索引
- 播放模式(单曲循环 / 列表循环 / 随机)
- 后台播放 & 通知栏控制(涉及
avSession,这里先不展开太细)
纯音频播放可以用 media.createAudioPlayer()(具体 API 略做简化示意):
import media from '@ohos.multimedia.media';
class AudioPlayerManager {
private player?: media.AVPlayer;
private list: Track[] = [];
private index: number = 0;
async setPlaylist(list: Track[]) {
this.list = list;
this.index = 0;
await this.prepareCurrent();
}
private async prepareCurrent() {
const track = this.list[this.index];
if (!this.player) {
this.player = await media.createAVPlayer(); // 或 createAudioPlayer,视 API 而定
this.bindEvents();
}
this.player.reset();
this.player.url = track.url;
await this.player.prepare();
}
play() {
this.player?.play();
}
pause() {
this.player?.pause();
}
next() {
if (this.index < this.list.length - 1) {
this.index++;
this.prepareCurrent().then(() => this.play());
}
}
prev() {
if (this.index > 0) {
this.index--;
this.prepareCurrent().then(() => this.play());
}
}
private bindEvents() {
this.player.on('finish', () => {
// 自动播下一首
this.next();
});
}
}
UI 层可以用一个全局 AudioPlayerManager + ArkUI 状态绑定,把状态映射到播放条上。
五、全屏 / 小窗播放:别把全屏逻辑散落在一堆 if 里
全屏 / 小窗逻辑,本质是两个问题:
VideoPlayer在哪里渲染(哪个布局、占多大区域)- 系统状态怎么配合(隐藏导航栏、横屏、沉浸等)
5.1 简单全屏方案:同一页面内布局切换
最简单的实现,全屏其实就是 “同一个页面里占满”,比如:
@Entry
@Component
struct VideoFullscreenDemo {
@State isFull: boolean = false;
build() {
Stack() {
// 正常布局
if (!this.isFull) {
Column() {
Text('标题区域').fontSize(18).margin(12)
VideoPlayer({ src: 'https://example.com/demo.mp4' })
.width('100%')
.height(220)
// 其他内容...
}
} else {
// 全屏模式:视频占满
VideoPlayer({ src: 'https://example.com/demo.mp4' })
.width('100%')
.height('100%')
}
// 浮动一个退出全屏按钮
if (this.isFull) {
Row() {
Button('退出全屏')
.onClick(() => this.isFull = false)
}
.position({ x: 16, y: 16 })
}
}
.width('100%')
.height('100%')
.backgroundColor('#000000')
}
}
实际项目里你会把 VideoPlayer 做成组件,并且让“全屏状态”从外部控制。
更进阶一点,可以考虑:
- 全屏时锁定横屏
- 调整系统 UI(状态栏透明 / 隐藏)
5.2 小窗播放(列表页)模式:一屏多个视频
常见:
- 短视频瀑布流,每个 Item 上都有一个视频预览
- 只有当前可见的某个 Item 在播放,滚出屏幕后暂停 / 切下一个
这种场景下,做好两件事:
-
不同时创建 N 个播放器
- 共享一个 AVPlayer + 不同 Surface
- 或限制同时播放的数量(比如仅一个)
-
跟踪“当前可见的视频项”
- 滚动时计算当前处于中心的 Item
- 切换播放源
这块因为涉及列表、滚动事件、可见性判断,属于另一个话题,可以单开一篇“短视频列表架构”,这次先不展开太深,只提醒一句:
如果你每个 Item 都 new 一个 Player,那不用分析,你的性能一定会很惨。
六、播放缓存策略:“秒开”和“别把用户流量吃死”之间的平衡
播放缓存分两层:
- 播放器内部缓冲:播放前后几秒的数据(你配置不多,更多走系统策略)
- 业务层离线缓存:提前下载 / 缓存到本地,下次直接本地播
6.1 播放器缓冲:更多是“观察 & 兜底”
你能直接控制的通常有限:
- 缓冲阈值(有些 API 提供设置)
- 是否允许边下边播(流媒体默认如此)
更实用的是:
- 监听缓冲状态,UI 上给明确提示(“当前网络较差,正在缓冲…”)
- 避免频繁 seek(尤其是直播流),减少卡顿
6.2 业务层缓存:短视频 / 音频列表大杀器
很多应用会做:
- 向下滑时,预加载接下来 2~3 条视频
- 经常播放的音频提前缓存一份
基本思路:
-
维护一个“缓存管理器”:
- 负责视频 / 音频文件下载
- 管理缓存目录 & 大小限制
-
播放前先查缓存:
- 有缓存:直接播本地文件路径
- 无缓存:用在线 URL 播 & 后台下载
伪代码:
class MediaCacheManager {
async getPlayableUrl(url: string): Promise<string> {
const cachedPath = await this.findInCache(url);
if (cachedPath) return cachedPath;
this.prefetch(url); // 后台下载,不阻塞首次播放
return url; // 先在线播放
}
async prefetch(url: string) {
// 下载并写入缓存目录
}
async cleanUpIfNeeded() {
// 控制缓存总大小
}
}
播放侧:
const playUrl = await MediaCacheManager.getPlayableUrl(originUrl);
this.src = playUrl;
这样在网络环境好的时候,用户很快就会享受到“第二次播放明显快”的体验。
小结:把音视频当“系统能力 + 业务模型”的组合来做
写到这里,你应该大概有这么一条主线了:
-
API 层:
- 音频:
AudioPlayer/AVPlayer - 视频:
VideoPlayer组件 + 控制器 - 流式播放:跟“播 URL”没本质区别,主要差在容错和 UI 提示
- 音频:
-
业务层模型:
- 定义自己的
PlayerState - 封装
VideoPlayerModel/AudioPlayerManager - 所有 UI 只是去读这个模型的“状态 + 控制方法”
- 定义自己的
-
体验层:
- 全屏 / 小窗:是布局切换问题,而不是“另一个页面”问题
- 缓存策略:播放器内部缓冲 + 业务层离线缓存
- 状态提示:缓冲、出错、结束都要有明确反馈
真正稳定好用的媒体体验,从来不是一两个 API 调用出来的,而是你在这几层之间组织得有多清晰——谁负责“能不能播”,谁负责“播得顺不顺”,谁负责“用户看见什么”。
如果觉得有帮助,别忘了点个赞+关注支持一下~
喜欢记得关注,别让好内容被埋没~
更多推荐


所有评论(0)