【HarmonyOS- 媒体技术 -AVPlayer】手把手教你用 AVPlayer 实现流媒体播放 (ArkTS 详解)
- 2025-11-03北京
本文字数:12949 字
阅读完需:约 42 分钟

2025 年最新实战指南|从零搭建稳定、流畅的直播/点播播放器
一句话总结
在 HarmonyOS 中,使用 AVPlayer 播放流媒体,不是“能播就行”,而是要“稳、准、快、可控”。本文带你掌握从创建到释放的全链路操作,覆盖 HLS/DASH/FLV 等主流协议,支持码率切换、轨道选择、自动重试、缓冲监控等高阶能力。
一、前置准备:权限 & 环境配置
1. 添加网络权限(必须!)
在 module.json5 中添加:
{ "reqPermissions": [ { "name": "ohos.permission.INTERNET" } ]}⚠️ 否则访问任何网络资源都会失败!
2. 引入 MediaKit 模块
import { media } from '@kit.MediaKit';
推荐使用 @kit.MediaKit,它是 HarmonyOS 官方提供的多媒体核心库。
二、标准播放流程(必看!)
顺序不能乱!否则可能收不到事件、无法播放
async avSetupStreamingMediaVideo() { if (this.context == undefined) return; // 创建avPlayer实例对象。 this.avPlayer = await media.createAVPlayer();
// 创建状态机变化回调函数。 await this.setAVPlayerCallback((avPlayer: media.AVPlayer) => { this.percent = avPlayer.width / avPlayer.height; // 计算并保存视频的宽高比 this.setVideoWH(); // 调用方法更新视频显示区域的宽高 this.durationTime = this.getDurationTime(); // 获取视频总时长 setInterval(() => { // 更新当前时间。 if (!this.isSwiping) { this.currentTime = this.getCurrentTime(); } }, SET_INTERVAL); });
// 设置播放资源。 this.avPlayer.url = "http://media.iyuns.top:1000/http/720p_1m.mp4"; //开始播放 avPlay(): void { if (this.avPlayer) { try { this.avPlayer.play(); } catch (e) { console.error(`${this.tag}: avPlay = ${JSON.stringify(e)}`); } } }三、核心监听事件详解(缺一不可)
事件 | 作用 | 说明 |
stateChange | 监听播放器状态变化 | 必要事件,监听播放器的state属性改变。 需要播放器在idle状态下、未调用设置资源接口前完成设置监听,若在调用设置资源接口后再设置监听,可能导致无法收到资源设置过程中上报的stateChange事件。 |
error | 捕获播放错误 | 网络异常、格式不支持、URL无效等,需要播放器在idle状态下、未调用设置资源接口前完成设置监听,若在调用设置资源接口后再设置监听,可能导致无法收到资源设置过程中上报的error事件。 |
durationUpdate | 获取总时长 | 监听进度条长度,刷新资源时长。 |
timeUpdate | 实时更新播放进度 | 监听进度条当前位置,刷新当前时间。 |
bufferingUpdate | 监控缓冲状态 | 监听网络播放缓冲信息,上报缓冲百分比以及缓存播放进度。 |
seekDone | seek 跳转完成通知 | 监听seek()请求完成情况。 当使用seek()跳转到指定播放位置后,如果seek操作成功,将上报该事件。 |
speedDone | 倍速设置完成通知 | 监听setSpeed()请求完成情况。 当使用setSpeed()设置播放倍速后,如果setSpeed操作成功,将上报该事件。 |
volumeChange | 音量调节完成反馈 | 监听setVolume()请求完成情况。 当使用setVolume()调节播放音量后,如果setVolume操作成功,将上报该事件。 |
audioInterrupt | 监听音频焦点切换信息 | 搭配属性audioInterruptMode使用。 如果当前设备存在多个音频正在播放,音频焦点被切换(即播放其他媒体如通话等)时将上报该事件,应用可以及时处理。 |
示例:监听播放器状态变化和监听播放时间
// 状态机变化回调函数。 this.avPlayer.on('stateChange', async (state, reason) => { if (this.avPlayer == null) { console.info(`${this.tag}: avPlayer has not init on state change`); return; } // 时间上报监听函数。 this.avPlayer.on('timeUpdate', (time: number) => { this.currentTime = time; });四、主流协议支持一览表
协议 | 是否支持 | 典型URL 示例 | 重点适用场景 |
HLS | ✅ 支持 | https://xxx/index.m3u8 | 直播、点播、CDN 分发、自适应码率、DRM 加密 |
DASH | ✅ 支持 | https://xxx.mpd | 直播、点播、CDN 分发、自适应码率、DRM 加密 |
HTTP/HTTPS | ✅ 支持 | https://xxx.mp4 | 点播、短片 |
HTTP-FLV | ✅ 支持 | https://xxx.flv | 低延迟直播(如游戏推流) |
✅ 所有协议均支持 setSource() 直接接入,无需额外封装。
五、高阶功能实战(让你的播放器“聪明”起来)
1. 流媒体缓冲状态
当下载速率低于片源的码率时,会出现卡顿。此时,播放器检测到缓冲区数据不足,会先缓冲一些数据再播放,避免连续卡顿。一次卡顿对应的缓冲事件上报过程为:BUFFERING_START-> BUFFERING_PERCENT 0 -> ... -> BUFFERING_PERCENT 100 -> BUFFERING_END。CACHED_DURATION 在卡顿过程和播放过程中都会持续上报,直至下载至资源末尾。
import { media } from '@kit.MediaKit';// 类成员定义avPlayerprivate avPlayer: media.AVPlayer | null = null;
// 创建avPlayer实例对象。this.avPlayer = await media.createAVPlayer();// 监听当前bufferingUpdate缓冲状态。this.avPlayer.on('bufferingUpdate', (infoType : media.BufferingInfoType, value : number) => { console.info(`AVPlayer bufferingUpdate, infoType is ${infoType}, value is ${value}.`);})
适用于直播、弱网环境下保障连续播放。
2. HLS 多码率切换(自定义清晰度)
当前流媒体 HLS 协议流支持多码率播放,默认情况下,播放器会根据网络下载速度选择合适的码率。
通过on('availableBitrates')监听当前 HLS 协议流可用的码率。如果监听的码率列表长度为 0,则不支持设置指定码率。
mport { media } from '@kit.MediaKit';// 类成员定义avPlayerprivate avPlayer: media.AVPlayer | null = null;
// 创建avPlayer实例对象。this.avPlayer = await media.createAVPlayer();// 监听当前HLS协议流可用的码率。this.avPlayer.on('availableBitrates', (bitrates: Array<number>) => { console.info('availableBitrates called, and availableBitrates length is: ' + bitrates.length);})通过setBitrate接口设置播放码率。若用户设置的码率不在可用码率中,播放器将选择最小且最接近的码率。该接口只能在 prepared/playing/paused/completed 状态下调用,可通过监听bitrateDone事件确认是否生效。
mport { media } from '@kit.MediaKit';// 类成员定义avPlayerprivate avPlayer: media.AVPlayer | null = null;
// 创建avPlayer实例对象。this.avPlayer = await media.createAVPlayer();// 监听当前HLS协议流可用的码率。this.avPlayer.on('availableBitrates', (bitrates: Array<number>) => { console.info('availableBitrates called, and availableBitrates length is: ' + bitrates.length);})
可配合 UI 提供“清晰度选择”按钮。
3. DASH 起播策略设置(首帧更快加载)
为了保证在弱网环境下的播放体验,AVPlayer 将默认选择最低的视频分辨率开始播放,随后依据网络状况自动调整。开发者可以根据具体需求,自定义 DASH 视频的起播策略,包括设定视频的宽度、高度以及色彩格式等参数。
// 自定义起播分辨率:1920×1080import { media } from '@kit.MediaKit';
let mediaSource : media.MediaSource = media.createMediaSourceWithUrl("http://test.cn/dash/aaa.mpd", {"User-Agent" : "User-Agent-Value"});let playbackStrategy : media.PlaybackStrategy = {preferredWidth: 1920, preferredHeight: 1080};this.avPlayer.setMediaSource(mediaSource, playbackStrategy);弱网环境下优先加载低码率,提升首帧速度。
4. DASH 音视频轨道切换(手动选清晰度/语言)
DASH 流媒体资源包含多路不同分辨率、码率、采样率、编码格式的音频、视频及字幕资源。默认情况下,AVPlayer 会依据网络状况自动切换不同码率的视频轨道。开发者可根据需求选择指定的音视频轨道播放,此时自适应码率切换策略将失效。
调用getTrackDescription获取所有音视频轨道列表。开发者可根据实际需求,基于MediaDescription各字段信息,确定目标轨道索引。
// 以获取1080p视频轨道索引为例。import { media } from '@kit.MediaKit';import { BusinessError } from '@kit.BasicServicesKit';public videoTrackIndex: number = 0;// 类成员定义avPlayerprivate avPlayer: media.AVPlayer | null = null;
// 创建avPlayer实例对象。this.avPlayer = await media.createAVPlayer();this.avPlayer.getTrackDescription((error: BusinessError, arrList: Array<media.MediaDescription>) => { if (arrList != null) { for (let i = 0; i < arrList.length; i++) { let propertyIndex: Object = arrList[i][media.MediaDescriptionKey.MD_KEY_TRACK_INDEX]; let propertyType: Object = arrList[i][media.MediaDescriptionKey.MD_KEY_TRACK_TYPE]; let propertyWidth: Object = arrList[i][media.MediaDescriptionKey.MD_KEY_WIDTH]; let propertyHeight: Object = arrList[i][media.MediaDescriptionKey.MD_KEY_HEIGHT]; if (propertyType == media.MediaType.MEDIA_TYPE_VID && propertyWidth == 1920 && propertyHeight == 1080) { this.videoTrackIndex = parseInt(propertyIndex?.toString()); // 获取1080p视频轨道索引。 } } } else { console.error(`getTrackDescription fail, error:${error}`); }});在音视频播放过程中调用selectTrack选择对应的音视频轨道,或者调用deselectTrack取消选择的音视频轨道。
import { media } from '@kit.MediaKit';public videoTrackIndex: number = 0; // 类成员定义avPlayerprivate avPlayer: media.AVPlayer | null = null;
// 创建avPlayer实例对象。this.avPlayer = await media.createAVPlayer();// 切换至目标视频轨道。this.avPlayer.selectTrack(this.videoTrackIndex);// 取消选择目标视频轨道。// this.avPlayer.deselectTrack(this.videoTrackIndex);
适合教育类、影视类 App,让用户自由选择画质/字幕/语音。
六、常见坑位 & 解决方案(避雷手册)
问题 | 原因 | 解法 |
play() 无效,无反应 | 没等 prepared 状态 | 必须监听 stateChange,在 prepared 后调 play() |
durationUpdate 不触发 | 未调用 prepare() 或资源无效 | 检查URL、网络、格式 |
字幕不显示 | 路径错误或未加载 | 用 addSubtitleFromFd() 加载 .srt 文件 |
无法跳转(seek) | 未监听 seekDone 或资源不支持 | 检查是否为分段资源(如HLS) |
多次播放报错 | 未释放旧实例 | 播放实例不使用后,调 release()及时释放。 |
音量调节无效 | 未监听 volumeChange 或未调 setVolume() | 检查调用顺序和参数 |
七、一个简单的开发实例
import { media } from '@kit.MediaKit';import { emitter } from '@kit.BasicServicesKit';import { display } from '@kit.ArkUI';
const TIME_ONE = 60000; // 1分钟的毫秒数。const TIME_TWO = 1000; // 1秒的毫秒数。const SET_INTERVAL = 1000; // 每秒更新一次当前播放时间。const SPEED_ZERO: number = 0; // 对应1.00x。const SPEED_ONE: number = 1; // 对应1.25x。const SPEED_TWO: number = 2; // 对应1.75x。const SPEED_THREE: number = 3; // 对应2.00x。const PROPORTION: number = 0.99;let innerEventFalse: emitter.InnerEvent = { eventId: 1, priority: emitter.EventPriority.HIGH};let innerEventTrue: emitter.InnerEvent = { eventId: 2, priority: emitter.EventPriority.HIGH};
let innerEventWH: emitter.InnerEvent = { eventId: 3, priority: emitter.EventPriority.HIGH};@Entry@Componentstruct Index { private avPlayer: media.AVPlayer | null = null; private context: Context | undefined = undefined; public videoTrackIndex: number = 0; public bitrate: number = 0; @State durationTime: number = 0; @State currentTime: number = 0; @State percent: number = 0; @State isSwiping: boolean = false; @State tag: string = 'StreamingMedia'; private surfaceId: string = ''; @State speedSelect: number = -1; public intervalID: number = -1; @State windowWidth: number = 300; @State windowHeight: number = 300; @State surfaceW: number | null = null; @State surfaceH: number | null = null; @State isPaused: boolean = true; @State XComponentFlag: boolean = false; getDurationTime(): number { return this.durationTime; }
getCurrentTime(): number { return this.currentTime; }
timeConvert(time: number): string { let min: number = Math.floor(time / TIME_ONE); let second: string = ((time % TIME_ONE) / TIME_TWO).toFixed(0); // return `${min}:${(+second < TIME_THREE ? '0' : '') + second}`; second = second.padStart(2, '0'); return `${min}:${second}`; }
async msleepAsync(ms: number): Promise<boolean> { return new Promise((resolve, reject) => { setTimeout(() => { resolve(true) }, ms) }) }
async avSetupStreamingMediaVideo() { if (this.context == undefined) return; // 创建avPlayer实例对象。 this.avPlayer = await media.createAVPlayer();
// 创建状态机变化回调函数。 await this.setAVPlayerCallback((avPlayer: media.AVPlayer) => { this.percent = avPlayer.width / avPlayer.height; this.setVideoWH(); this.durationTime = this.getDurationTime(); setInterval(() => { // 更新当前时间。 if (!this.isSwiping) { this.currentTime = this.getCurrentTime(); } }, SET_INTERVAL); });
// 情况一:HTTP视频播放。 this.avPlayer.url = "http://media.iyuns.top:1000/http/720p_1m.mp4";
// 情况二:HLS视频播放。 // this.avPlayer.url = "http://media.iyuns.top:1000/720-270-480.m3u8";
// 情况三:DASH视频播放。 // this.avPlayer.url = "http://media.iyuns.top:1000/dash/720p/720-1/720-1.mpd";
// 情况四:通过setMediaSource设置自定义头域及播放优选参数实现初始播放参数设置,以流媒体HTTP点播为例。 /* let mediaSource : media.MediaSource = media.createMediaSourceWithUrl("http://media.iyuns.top:1000/http/720p_1m.mp4", {"":""}); // 设置播放策略,设置为缓冲区数据为20s。 let playbackStrategy : media.PlaybackStrategy = {preferredBufferDuration: 20}; // 为avPlayer设置媒体来源和播放策略。 this.avPlayer.setMediaSource(mediaSource, playbackStrategy); * */
// 情况五:HLS切码率。 /* this.avPlayer.url = "https://upftimae.dailyworkout.cn/videos/course/c800f81a209b5ee7891f1128ed301db/4/master.m3u8"; let bitrate: number = 0; // 监听当前HLS协议流可用的码率。 this.avPlayer.on('availableBitrates', (bitrates: Array<number>) => { console.info('availableBitrates called, and availableBitrates length is: ' + bitrates.length); this.bitrate = bitrates[0]; // 保存需要切换的码率。 }) // 监听码率设置是否生效。 this.avPlayer.on('bitrateDone', (bitrate: number) => { console.info('bitrateDone called, and bitrate value is: ' + bitrate); }) * */
// 情况六:DASH切换音视频轨道。 /* this.avPlayer.url = "http://poster-inland.hwcloudtest.cn/AiMaxEngine/ProductionEnvVideo/DASH_SDR_MultiAudio_MultiSubtitle_yinHeHuWeiDui3/DASH_SDR_MultiAudio_MultiSubtitle_yinHeHuWeiDui3.mpd"; // this.avPlayer.getTrackDescription((error: BusinessError, arrList: Array<media.MediaDescription>) => { if (arrList != null) { for (let i = 0; i < arrList.length; i++) { let propertyIndex: Object = arrList[i][media.MediaDescriptionKey.MD_KEY_TRACK_INDEX]; let propertyType: Object = arrList[i][media.MediaDescriptionKey.MD_KEY_TRACK_TYPE]; let propertyWidth: Object = arrList[i][media.MediaDescriptionKey.MD_KEY_WIDTH]; let propertyHeight: Object = arrList[i][media.MediaDescriptionKey.MD_KEY_HEIGHT]; if (propertyType == media.MediaType.MEDIA_TYPE_VID && propertyWidth == 1920 && propertyHeight == 1080) { this.videoTrackIndex = parseInt(propertyIndex.toString()); // 获取1080p视频轨道索引。 } } } else { console.error(`getTrackDescription fail, error:${error}`); } }); * */ }
// HLS切换码率。 changeBitrate(bitrate: number) { if (this.avPlayer == null) { return; } // 设置播放码率。 try { this.avPlayer.setBitrate(bitrate); } catch (error) { console.error(`${this.tag}: setBitrate failed, error message is = ${JSON.stringify(error.message)}`); } }
// DASH切换音视频轨道。 changeTrack(track: number) { if (this.avPlayer == null) { return; } // 切换至目标视频轨道。 try { this.avPlayer.selectTrack(track); } catch (error) { console.error(`${this.tag}: selectTrack failed, error message is = ${JSON.stringify(error.message)}`); } // 取消选择目标视频轨道。 /* try { this.avPlayer.deselectTrack(track); } catch (error) { console.error(`${this.tag}: deselectTrack failed, error message is = ${JSON.stringify(error.message)}`); } * */ }
avPlay(): void { if (this.avPlayer) { try { this.avPlayer.play(); } catch (e) { console.error(`${this.tag}: avPlay = ${JSON.stringify(e)}`); } } }
avPause(): void { if (this.avPlayer) { try { this.avPlayer.pause(); console.info(`${this.tag}: avPause==`); } catch (e) { console.error(`${this.tag}: avPause== ${JSON.stringify(e)}`); } } }
async avSeek(seekTime: number, mode: SliderChangeMode): Promise<void> { if (this.avPlayer) { try { console.info(`${this.tag}: videoSeek seekTime== ${seekTime}`); this.avPlayer.seek(seekTime, 2); this.currentTime = seekTime; } catch (e) { console.error(`${this.tag}: videoSeek== ${JSON.stringify(e)}`); } } }
avSetSpeed(speed: number): void { if (this.avPlayer) { try { this.avPlayer.setSpeed(speed); console.info(`${this.tag}: avSetSpeed enum ${speed}`); } catch (e) { console.error(`${this.tag}: avSetSpeed == ${JSON.stringify(e)}`); } } }
// 注册avplayer回调函数。 async setAVPlayerCallback(callback: (avPlayer: media.AVPlayer) => void, vType?: number): Promise<void> { // seek操作结果回调函数。 if (this.avPlayer == null) { console.error(`${this.tag}: avPlayer has not init!`); return; } this.avPlayer.on('seekDone', (seekDoneTime) => { console.info(`${this.tag}: setAVPlayerCallback AVPlayer seek succeeded, seek time is ${seekDoneTime}`); }); this.avPlayer.on('speedDone', (speed) => { console.info(`${this.tag}: setAVPlayerCallback AVPlayer speedDone, speed is ${speed}`); }); // error回调监听函数,当avPlayer在操作过程中出现错误时调用reset接口触发重置流程。 this.avPlayer.on('error', (err) => { console.error(`${this.tag}: setAVPlayerCallback Invoke avPlayer failed ${JSON.stringify(err)}`); if (this.avPlayer == null) { console.error(`${this.tag}: avPlayer has not init on error`); return; } this.avPlayer.reset(); }); // 状态机变化回调函数。 this.avPlayer.on('stateChange', async (state, reason) => { if (this.avPlayer == null) { console.info(`${this.tag}: avPlayer has not init on state change`); return; } switch (state) { case 'idle': // 成功调用reset接口后触发该状态机上报。 console.info(`${this.tag}: setAVPlayerCallback AVPlayer state idle called.`); break; case 'initialized': // avplayer 设置播放源后触发该状态上报。 console.info(`${this.tag}: setAVPlayerCallback AVPlayer state initialized called.`); if (this.surfaceId) { this.avPlayer.surfaceId = this.surfaceId; // 设置显示画面,当播放的资源为纯音频时无需设置。 console.info(`${this.tag}: setAVPlayerCallback this.avPlayer.surfaceId = ${this.avPlayer.surfaceId}`); this.avPlayer.prepare(); } break; case 'prepared': // prepare调用成功后上报该状态机。 console.info(`${this.tag}: setAVPlayerCallback AVPlayer state prepared called.`); this.avPlayer.on('bufferingUpdate', (infoType: media.BufferingInfoType, value: number) => { console.info(`${this.tag}: bufferingUpdate called, infoType value: ${infoType}, value:${value}}`); }) this.durationTime = this.avPlayer.duration; this.currentTime = this.avPlayer.currentTime; this.avPlayer.play(); // 调用播放接口开始播放。 console.info(`${this.tag}: setAVPlayerCallback speedSelect: ${this.speedSelect}, duration: ${this.durationTime}`); if (this.speedSelect != -1) { switch (this.speedSelect) { case SPEED_ZERO: this.avSetSpeed(media.PlaybackSpeed.SPEED_FORWARD_1_00_X); break; case SPEED_ONE: this.avSetSpeed(media.PlaybackSpeed.SPEED_FORWARD_1_25_X); break; case SPEED_TWO: this.avSetSpeed(media.PlaybackSpeed.SPEED_FORWARD_1_75_X); break; case SPEED_THREE: this.avSetSpeed(media.PlaybackSpeed.SPEED_FORWARD_2_00_X); break; } } callback(this.avPlayer); break; case 'playing': // play成功调用后触发该状态机上报。 console.info(`${this.tag}: setAVPlayerCallback AVPlayer state playing called.`); if (this.intervalID != -1) { clearInterval(this.intervalID) } this.intervalID = setInterval(() => { // 更新当前时间。 AppStorage.setOrCreate('durationTime', this.durationTime); AppStorage.setOrCreate('currentTime', this.currentTime); }, 100); let eventDataTrue: emitter.EventData = { data: { 'flag': true } }; let innerEventTrue: emitter.InnerEvent = { eventId: 2, priority: emitter.EventPriority.HIGH }; emitter.emit(innerEventTrue, eventDataTrue); break; case 'completed': // 播放结束后触发该状态机上报。 console.info(`${this.tag}: setAVPlayerCallback AVPlayer state completed called.`); let eventDataFalse: emitter.EventData = { data: { 'flag': false } }; let innerEvent: emitter.InnerEvent = { eventId: 1, priority: emitter.EventPriority.HIGH }; emitter.emit(innerEvent, eventDataFalse); if (this.intervalID != -1) { clearInterval(this.intervalID) } this.avPlayer.off('bufferingUpdate') AppStorage.setOrCreate('currentTime', this.durationTime); break; case 'released': console.info(`${this.tag}: setAVPlayerCallback released called.`); break case 'stopped': console.info(`${this.tag}: setAVPlayerCallback AVPlayer state stopped called.`); break case 'error': console.error(`${this.tag}: setAVPlayerCallback AVPlayer state error called.`); break case 'paused': console.info(`${this.tag}: setAVPlayerCallback AVPlayer state paused called.`); break default: console.info(`${this.tag}: setAVPlayerCallback AVPlayer state unknown called.`); break; } }); // 时间上报监听函数。 this.avPlayer.on('timeUpdate', (time: number) => { this.currentTime = time; }); }
aboutToAppear() { this.windowWidth = display.getDefaultDisplaySync().width; this.windowHeight = display.getDefaultDisplaySync().height; if (this.percent >= 1) { // 横向视频。 this.surfaceW = Math.round(this.windowWidth * PROPORTION); this.surfaceH = Math.round(this.surfaceW / this.percent); } else { // 纵向视频。 this.surfaceH = Math.round(this.windowHeight * PROPORTION); this.surfaceW = Math.round(this.surfaceH * this.percent); } this.isPaused = true; this.context = this.getUIContext().getHostContext(); }
aboutToDisappear() { if (this.avPlayer == null) { console.info(`${this.tag}: avPlayer has not init aboutToDisappear`); return; } this.avPlayer.release((err) => { if (err == null) { console.info(`${this.tag}: videoRelease release success`); } else { console.error(`${this.tag}: videoRelease release failed, error message is = ${JSON.stringify(err.message)}`); } }); emitter.off(innerEventFalse.eventId); }
onPageHide() { this.avPause(); this.isPaused = false; }
onPageShow() { emitter.on(innerEventTrue, (res: emitter.EventData) => { if (res.data) { this.isPaused = res.data.flag; this.XComponentFlag = res.data.flag; } }); emitter.on(innerEventFalse, (res: emitter.EventData) => { if (res.data) { this.isPaused = res.data.flag; } }); emitter.on(innerEventWH, (res: emitter.EventData) => { if (res.data) { this.windowWidth = res.data.width; this.windowHeight = res.data.height; this.setVideoWH(); } }); }
setVideoWH(): void { if (this.percent >= 1) { // 横向视频。 this.surfaceW = Math.round(this.windowWidth * PROPORTION); this.surfaceH = Math.round(this.surfaceW / this.percent); } else { // 纵向视频。 this.surfaceH = Math.round(this.windowHeight * PROPORTION); this.surfaceW = Math.round(this.surfaceH * this.percent); } }
@Builder CoverXComponent() { // ... }
build() { // ... }}八、立即行动,开启你的音视频播放开发之旅!
点击了解完整开发示例与 API 文档HarmonyOS AVPlayer 官方文档
加入 HarmonyOS 社区,共创未来!
我们诚邀广大开发者一起参与 HarmonyOS 技术生态建设,共建更开放、更智能的未来世界!
加入开发者社区,获取最新资讯和技术支持HarmonyOS 官方社区
如果你觉得这篇指南有用,欢迎点赞、收藏、分享给更多开发者! 让 AVPlayer 成为你开发路上的得力助手,开启你的音视频播放新纪元!
推荐阅读
电子书

大厂实战PPT下载
换一换 
杨学锐 | 阶跃星辰 语音模型负责人
郑纬民 | 中国工程院院士,清华大学教授,博士生导师
姚远 | 面壁智能 研究员 & 清华大学计算机系博士后






评论