HarmonyOS鸿蒙Next中列表播放视频全屏方案探讨:使用xcomponent作为surface
HarmonyOS鸿蒙Next中列表播放视频全屏方案探讨:使用xcomponent作为surface 列表中可以播放视频 如果全屏的话 有什么好的方案吗?我用的是 xcomponent 作为 surface
可以参考以下demo:
import { myVideoSourceDate, VideoSource } from './myVideoSourceData';
import { VideoComponent } from './VideoComponent';
@Entry
@Component
struct XCAvplayer {
private data: myVideoSourceDate = new myVideoSourceDate([]);
@State isLayoutFullScreen : boolean = false;
@State fangDaIndex : number = -1;
aboutToAppear(): void {
let list: VideoSource[] = [
new VideoSource("文案1", "video1.mp4"),
new VideoSource("文案2", "video2.mp4"),
new VideoSource("文案3", "video3.mp4"),
new VideoSource("文案4", "video4.mp4")
];
console.log("myAppliction is Appear");
this.data = new myVideoSourceDate(list);
}
build() {
Scroll() {
Column() {
List() {
LazyForEach(this.data, (item: VideoSource,index : number) => {
ListItem() {
VideoComponent({item : item, isLayoutFullScreen : this.isLayoutFullScreen, index : index , fangDaIndex : this.fangDaIndex})
.visibility(this.isLayoutFullScreen && this.fangDaIndex !== index ? Visibility.None : Visibility.Visible)
}
}, (item: string) => item)
}
.cachedCount(5)
.scrollBar(BarState.Off)
.edgeEffect(this.isLayoutFullScreen ? EdgeEffect.None : EdgeEffect.Spring)
}
}
.edgeEffect(this.isLayoutFullScreen ? EdgeEffect.None : EdgeEffect.Spring)
.width('100%')
}
}
AVPlayerDemo.ets
import media from '@ohos.multimedia.media';
import {BusinessError} from '@ohos.base';
import { ifaa } from '@kit.OnlineAuthenticationKit';
import { common } from '@kit.AbilityKit';
export class AVPlayerDemo {
private count: number = 0;
private surfaceID: string = ''; // surfaceID用于播放画面显示,具体的值需要通过Xcomponent接口获取,相关文档链接见上面Xcomponent创建方法。
private isSeek: boolean = true; // 用于区分模式是否支持seek操作。
private avPlayer: media.AVPlayer | undefined = undefined;
setSurfaceID(surface_id: string){
console.log('setSurfaceID : ' + surface_id);
this.surfaceID = surface_id;
}
// 注册avplayer回调函数。
setAVPlayerCallback(avPlayer: media.AVPlayer) {
// seek操作结果回调函数。
avPlayer.on('seekDone', (seekDoneTime: number) => {
console.info(`AVPlayer seek succeeded, seek time is ${seekDoneTime}`);
})
// error回调监听函数,当avplayer在操作过程中出现错误时,调用reset接口触发重置流程。
avPlayer.on('error', (err: BusinessError) => {
console.error(`Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`);
avPlayer.reset();
})
// 状态机变化回调函数。
avPlayer.on('stateChange', async (state: string, reason: media.StateChangeReason) => {
switch (state) {
case 'idle': // 成功调用reset接口后触发该状态机上报。
console.info('AVPlayer state idle called.');
avPlayer.release(); // 调用release接口销毁实例对象。
break;
case 'initialized': // avplayer 设置播放源后触发该状态上报。
console.info('AVPlayer state initialized called.');
avPlayer.surfaceId = this.surfaceID; // 设置显示画面,当播放的资源为纯音频时无需设置。
avPlayer.prepare();
break;
case 'prepared': // prepared调用成功后上报该状态机。
console.info('AVPlayer state prepared called.');
avPlayer.play();
break;
case 'playing': // play成功调用后触发该状态机上报。
console.info('AVPlayer state playing called.');
avPlayer.play();
break;
case 'paused': // pause成功调用后触发该状态机上报。
console.info('AVPlayer state paused called.');
break;
case 'completed': //播放接口后触发该状态机上报。
console.info('AVPlayer state completed called.');
avPlayer.play(); // 调用播放接口接口。
break;
case 'stopped': // stop接口后触发该状态机上报。
console.info('AVPlayer state stopped called.');
avPlayer.reset(); // 调用reset接口初始化avplayer状态。
break;
case 'released': //播放接口后触发该状态机上报。
console.info('AVPlayer state released called.');
break;
default:
break;
}
})
}
// 通过url设置网络地址来实现播放直播码流。
async avPlayerLiveDemo(count : number ,url: string){
this.count = count
// async avPlayerLiveDemo(){
// 创建avPlayer实例对象
this.avPlayer = await media.createAVPlayer();
// 创建状态机变化回调函数。
this.setAVPlayerCallback(this.avPlayer);
let context = getContext(this) as common.UIAbilityContext;
let fileDescriptor = await context.resourceManager.getRawFd(url);
let avFileDescriptor: media.AVFileDescriptor =
{ fd: fileDescriptor.fd, offset: fileDescriptor.offset, length: fileDescriptor.length };
this.avPlayer.fdSrc = avFileDescriptor;
}
async release() {
this.avPlayer?.reset();
}
getStage(): string {
if (this.avPlayer !== undefined) {
return this.avPlayer.state;
}
return 'undefined';
}
}
myVideoSourceData.ets
export class myVideoSourceDate implements IDataSource {
videoList: VideoSource[] = [];
constructor(videoList: VideoSource[]) {
this.videoList = videoList;
}
totalCount(): number {
return this.videoList.length;
}
getData(index: number): VideoSource {
return this.videoList[index];
}
registerDataChangeListener(listener: DataChangeListener): void {}
unregisterDataChangeListener(listener: DataChangeListener): void {}
}
@Observed
export class VideoSource {
text: string;
url: string;
constructor(text: string,url: string) {
this.text = text;
this.url = url;
}
}
VideoComponent.ets
import { AVPlayerDemo } from './AVPlayerDemo';
import { VideoSource } from './myVideoSourceData';
import { window } from '@ohos.window';
import { BusinessError } from '@ohos.base';
import { router } from '@kit.ArkUI';
let isPlaying: AVPlayerDemo[] = [];
@Component
export struct VideoComponent {
@ObjectLink item: VideoSource;
index : number = -1;
@Link isLayoutFullScreen: boolean;
@Link fangDaIndex : number;
@State bkColor: Color = Color.Red
mXComponentController: XComponentController = new XComponentController();
@State player_changed: boolean = false;
@State isLandScape: boolean = false;
player?: AVPlayerDemo;
setR(orientation: number) {
window.getLastWindow(getContext(this)).then(win => {
win.setPreferredOrientation(orientation).then(data => {
console.log('setWindowOrientation: ' + orientation + ' Succeeded. Data: ' + JSON.stringify(data));
}).catch(err => {
console.log('setWindowOrientation: Failed. Cause: ' + JSON.stringify(err));
});
}).catch(err => {
console.log('setWindowOrientation: Failed to obtain the top window. Cause: ' + JSON.stringify(err));
});
}
setFullScreen(isLayoutFullScreen: boolean) {
window.getLastWindow(getContext(this)).then(win => {
win.setWindowLayoutFullScreen(isLayoutFullScreen, (err: BusinessError) => {
const errCode: number = err.code;
if (errCode) {
console.error('Failed to set the window layout to full-screen mode. Cause:' + JSON.stringify(err));
return;
}
console.info('Succeeded in setting the window layout to full-screen mode.');
});
}).catch(err => {
console.log('setWindowOrientation: Failed to obtain the top window. Cause: ' + JSON.stringify(err));
});
}
build() {
//通过显隐控制控制其他listItem是否展示
Column() {
Text(this.item.text)
.visibility(this.isLayoutFullScreen === false ? Visibility.Visible : Visibility.None)
Stack() {
XComponent({ id: 'video_player_id', type: XComponentType.SURFACE, controller: this.mXComponentController })
.onLoad(() => {
this.player = new AVPlayerDemo();
this.player.setSurfaceID(this.mXComponentController.getXComponentSurfaceId());
// this.player_changed = !this.player_changed;
// this.player.avPlayerLiveDemo(0, this.item.url)
})
.height(this.isLayoutFullScreen ? (this.isLandScape ? '100%' : 200) : "100%")
Row() {
Button(this.player && (this.player.getStage() === 'playing') ? '播放中' : '开始')
.onClick(async () => {
if (isPlaying.length !== 0) {
let play = isPlaying.pop();
await play?.release();
}
if (this.player) {
this.player.avPlayerLiveDemo(0, this.item.url);
isPlaying.push(this.player);
}
})
.backgroundColor(this.bkColor)
Button("点击全屏")
.onClick(() => {
this.fangDaIndex = this.index;
this.isLayoutFullScreen = true;
this.setFullScreen(this.isLayoutFullScreen)
})
.backgroundColor(this.bkColor)
Button("退出全屏")
.onClick(() => {
this.setR(1);
this.isLayoutFullScreen = false;
this.isLandScape = false;
this.setFullScreen(this.isLayoutFullScreen)
})
.backgroundColor(this.bkColor)
Button("横屏")
.onClick(() => {
this.fangDaIndex = this.index;
this.setR(4);
this.isLandScape = true;
this.isLayoutFullScreen = true;
this.setFullScreen(this.isLayoutFullScreen)
})
.backgroundColor(this.bkColor)
Button("退出横屏")
.onClick(() => {
this.setR(1);
this.isLandScape = false;
})
.backgroundColor(this.bkColor)
}
}
.backgroundColor(Color.Black)
.height(this.isLayoutFullScreen ? "100%" : 200)
}
.onVisibleAreaChange([0.2, 1.0], async (isVisible: boolean, currentRatio: number) => {
if (!isVisible && currentRatio < 0.2) {
if (this.player && isPlaying.length !== 0 && this.player === isPlaying[0]) {
console.info('onVisibleAreaChange')
this.player.release();
isPlaying[0].release();
isPlaying.pop();
}
}
})
.width('100%')
}
}
更多关于HarmonyOS鸿蒙Next中列表播放视频全屏方案探讨:使用xcomponent作为surface的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
在HarmonyOS鸿蒙Next中,使用XComponent作为Surface来实现列表播放视频全屏的方案,主要涉及以下几个步骤:
-
创建XComponent:在布局文件中添加
XComponent组件,用于承载视频播放的Surface。XComponent是鸿蒙系统提供的一个组件,可以用来创建和管理Surface,Surface是绘制视频内容的载体。 -
初始化XComponent:在代码中初始化
XComponent,并通过XComponent获取Surface对象。Surface对象可以通过XComponent的getXComponentSurface()方法获取。 -
配置视频播放器:使用鸿蒙提供的
MediaPlayer或AVPlayer来播放视频。将Surface对象设置给播放器,视频内容将直接渲染到Surface上。 -
处理全屏逻辑:当用户点击视频时,通过调整
XComponent的布局参数(如宽度、高度、位置等)来实现全屏效果。可以通过监听点击事件,动态修改XComponent的布局属性。 -
恢复列表模式:在全屏状态下,用户退出全屏时,重新调整
XComponent的布局参数,使其恢复到列表中的原始位置和大小。 -
生命周期管理:在页面生命周期变化时(如页面隐藏、销毁等),确保正确释放
Surface和播放器资源,避免内存泄漏。
通过以上步骤,可以在HarmonyOS鸿蒙Next中实现列表播放视频的全屏功能。XComponent作为Surface的载体,能够高效地管理视频渲染,并通过动态调整布局参数实现全屏切换。
在HarmonyOS鸿蒙Next中,使用XComponent作为Surface实现列表视频全屏播放的方案如下:
-
XComponent初始化:在列表项中创建XComponent,并设置其surface类型为视频渲染。
-
视频播放控制:使用MediaPlayer绑定XComponent的Surface,进行视频加载与播放。
-
全屏切换:通过监听XComponent的点击事件,动态调整其布局参数至全屏,并暂停列表中其他视频的播放。
-
性能优化:利用HarmonyOS的分布式能力,确保视频播放流畅,同时减少内存占用。
此方案高效利用了鸿蒙系统的特性,确保了视频播放的流畅性和用户体验。


