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';
// 類成員定義avPlayer
private 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,則不支持設置指定碼率。
import { media } from '@kit.MediaKit';
// 類成員定義avPlayer
private 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事件確認是否生效。
import { media } from '@kit.MediaKit';
// 類成員定義avPlayer
private avPlayer: media.AVPlayer | null = null;
// 創建avPlayer實例對象。
this.avPlayer = await media.createAVPlayer();
// 監聽碼率設置是否生效。
this.avPlayer.on('bitrateDone', (bitrate: number) => {
console.info('bitrateDone called, and bitrate value is: ' + bitrate);
})
// 設置播放碼率。
this.bitrate: number = 96000;
this.avPlayer.setBitrate(this.bitrate);
可配合 UI 提供"清晰度選擇"按鈕。
3. DASH 起播策略設置(首幀更快加載)
為了保證在弱網環境下的播放體驗,AVPlayer將默認選擇最低的視頻分辨率開始播放,隨後依據網絡狀況自動調整。開發者可以根據具體需求,自定義DASH視頻的起播策略,包括設定視頻的寬度、高度以及色彩格式等參數。
// 自定義起播分辨率:1920×1080
import { 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會依據網絡狀況自動切換不同碼率的視頻軌道。開發者可根據需求選擇指定的音視頻軌道播放,此時自適應碼率切換策略將失效。
設置selectTrack生效的監聽事件trackChange。
import { media } from '@kit.MediaKit';
// 類成員定義avPlayer
private avPlayer: media.AVPlayer | null = null;
// 創建avPlayer實例對象。
this.avPlayer = await media.createAVPlayer();
this.avPlayer.on('trackChange', (index: number, isSelect: boolean) => {
console.info(`trackChange info, index: ${index}, isSelect: ${isSelect}`);
})
});
調用getTrackDescription獲取所有音視頻軌道列表。開發者可根據實際需求,基於MediaDescription各字段信息,確定目標軌道索引。
// 以獲取1080p視頻軌道索引為例。
import { media } from '@kit.MediaKit';
import { BusinessError } from '@kit.BasicServicesKit';
public videoTrackIndex: number = 0;
// 類成員定義avPlayer
private 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;
// 類成員定義avPlayer
private 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
@Component
struct 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 成為你開發路上的得力助手,開啓你的音視頻播放新紀元!