一、简单的视频播放、直播播放
1. 使用meida
中的avPlayer
结合XComponent
进行视频播放
如果是音频只需要一个路径就差不多了,这是音频HDI+显示HDI,所以需要做以下几点:
-
- 应用从XComponent组件获取窗口SurfaceID,获取方式参考XComponent。
-
- 应用把媒体资源、SurfaceID传递给AVPlayer接口。
-
- Player Framework把视频ES数据流输出给解码HDI,解码获得视频帧(NV12/NV21/RGBA)。
-
- Player Framework把音频PCM数据流输出给Audio Framework,Audio Framework输出给音频HDI。
-
- Player Framework把视频帧(NV12/NV21/RGBA)输出给Graphic Framework,Graphic Framework输出给显示HDI。
let mXComponentController: XComponentController = new XComponentController(); @Entry
...avPlayer: media.AVPlayer = Object()
//xcomponentController: XComponentController = new XComponentController()
url: string ="https://vdept3.bdstatic.com/mda-rb9vxh2uiq5jq71p/cae_h264/1739221710340328239/mda-rb9vxh2uiq5jq71p.mp4?v_from_s=hkapp-haokan-nanjing&auth_key=1743244119-0-0-7e704e3b242ea6b12d1825943add20cb&bcevod_channel=searchbox_feed&cr=0&cd=0&pd=1&pt=3&logid=1719267439&vid=8058861658488074906&klogid=1719267439&abtest="//media.createAVPlayer()
async playStart() {let avPlayer = await media.createAVPlayer()this.avPlayer = avPlayeravPlayer.on('stateChange', (state) => {if (state == 'initialized') {const id = mXComponentController.getXComponentSurfaceId()avPlayer.surfaceId = id//this.xcomponentController.setXComponentSurfaceRect({surfaceWidth: avPlayer.width, surfaceHeight: avPlayer.height})avPlayer.prepare()}if (state == 'prepared') {avPlayer.loop = trueavPlayer.play()}})avPlayer.url = this.url//avPlayer.url='https://vdept3.bdstatic.com/mda-rbm39nfss0emhmzs/cae_h264/1740191336755572797/mda-rbm39nfss0emhmzs.mp4?v_from_s=hkapp-haokan-nanjing&auth_key=1743241772-0-0-501f03b3ade825e7629e56a8326832aa&bcevod_channel=searchbox_feed&pd=1&cr=0&cd=0&pt=3&logid=2972294090&vid=12649103641022590803&klogid=2972294090&abtest='
}aboutToAppear(): void {this.playStart()
}aboutToDisappear(): void {this.avPlayer.stop()this.avPlayer.release()
}async pipShow(){pipController = await PiPWindow.create(config)pipController.startPiP()pipController.setAutoStartEnabled(true)
}build() {NavDestination() {Column() {MkNavbar({title: '视频',leftClickHandler: () => {// 点击返回this.pageStack.pop()}})Column({ space: 8 }) {// Button('开始播放')// .onClick(() => {// this.playStart()// })Button('暂停播放').onClick(() => {this.avPlayer.pause()})Button('继续播放').onClick(() => {this.avPlayer.play()})XComponent({ type: XComponentType.SURFACE, controller: mXComponentController }).width('100%').height(300)Button('随机播放视频').onClick(async () => {this.url = this.list[Math.floor(Math.random() * this.list.length)]await this.avPlayer.reset()this.playStart()})}}.width('100%').height('100%').backgroundColor($r('[basic].color.under'))}.hideTitleBar(true).id('gtxy4869')}
}
2. 使用Video
组件做一个直播播放,上下滑动切换直播
① 主要了解一下VideoOptions
里面配置的参数作用
-
src
:Resource格式可以跨包/跨模块访问资源文件,常用于访问本地视频。支持rawfile文件下的资源,即通过$rawfile引用视频文件。string格式可用于加载网络视频和本地视频,常用于加载网络视频。支持网络视频地址。支持file://路径前缀的字符串,即应用沙箱URI:file:///。用于读取应用沙箱路径内的资源。需要保证目录包路径下的文件有可读权限。 -
currentProgressRate
:视频播放倍速。说明:number取值仅支持:0.75,1.0,1.25,1.75,2.0。 -
previewUri
:视频未播放时的预览图片路径,默认不显示图片。 -
controller
:设置视频控制器,可以控制视频的播放状态。(重点)类型为:VideoController
② VideoController
上的方法可以控制我们的video
-
start() 开始播放。
-
pause() 暂停播放,显示当前帧,再次播放时从当前位置继续播放。
-
stop() 停止播放,显示当前帧,再次播放时从头开始播放。
-
reset(): void video组件重置AVPlayer。显示当前帧,再次播放时从头开始播放。
-
setCurrentTime(value: number) 指定视频播放的进度位置。
-
requestFullscreen(value: boolean) 请求全屏播放。
-
exitFullscreen() 退出全屏播放。
-
setCurrentTime(value: number, seekMode: SeekMode) 指定视频播放的进度位置,并指定跳转模式。
import { http } from '@kit.NetworkKit';let httpRequest = http.createHttp();function Live() {return httpRequest.request('http://123.60.114.86:8090/goods/liveInfo?id=2',{header: {'Content-Type': 'application/json'},readTimeout: 60000,connectTimeout: 60000});
}class LiveInfoResponse {code: number = 0;data: Array<Info> = [];
}class Info {uri: string = '';name: string = '';peopleNum: string = '';
}
interface ILiveInfoDataModel {uri: string | Resource;name: string | Resource;peopleNum: string | Resource;
}class LiveInfoDataModel {public uri: string | Resource;public name: string | Resource;public peopleNum: string | Resource;constructor(item: ILiveInfoDataModel) {this.uri = item.uri;this.name = item.name;this.peopleNum = item.peopleNum;}
}
const LiveData: Array<ILiveInfoDataModel> = [{uri: $rawfile('video1.mp4'),name: $r('app.string.first_author'),peopleNum: '520'},{uri: $rawfile('video2.mp4'),name: $r('app.string.two_author'),peopleNum: '360'},{uri: $rawfile('video3.mp4'),name: $r('app.string.three_author'),peopleNum: '777'}
];
@Entry
@Component
struct PageDouyin {@State active: number = 0;@State liveInfoList: Array<LiveInfoDataModel> = [];@State mData: LiveInfoResponse = {code: 0,data: [{ uri: '', name: '', peopleNum: '' }]}@State isPhone: boolean = false;portraitFunc: Callback<mediaquery.MediaQueryResult> = (mediaQueryResult: mediaquery.MediaQueryResult): void => {this.onPortrait(mediaQueryResult);};listenerIsPhone = mediaquery.matchMediaSync('(orientation:landscape)');//屏幕方向监听onPortrait(mediaQueryResult: mediaquery.MediaQueryResult) {this.isPhone = !mediaQueryResult.matches;}async aboutToAppear() {LiveData.forEach((item) => {this.liveInfoList.push(new LiveInfoDataModel(item));})this.listenerIsPhone.on('change', this.portraitFunc);try {let a = await Live();this.mData = JSON.parse(a.result.toString());this.liveInfoList = this.mData.data;} catch (error) {console.log('http resquest is fail:' + error);}}build() {Scroll() {Column() {Swiper() {ForEach(this.liveInfoList, (item: LiveInfoDataModel, index) => {Stack() {if (this.active === index) {Video({ src: item.uri }).autoPlay(true).loop(false).controls(false).objectFit(ImageFit.Contain).width('100%').height('100%')}Row() {Row() {Row() {Image($r('app.media.live_author')).width(38).height(38)Column() {Text(item.name).fontSize(16).fontColor('#ffffff')Row() {Text(item.peopleNum).id(item.peopleNum as string).fontSize(12).fontColor('#ffffff')Text($r('app.string.watch')).fontSize(12).fontColor('#ffffff')}}.alignItems(HorizontalAlign.Start).padding({ left: '2%' })}Button($r('app.string.follow')).backgroundColor(Color.Red).height(35).width(70)}.justifyContent(FlexAlign.SpaceBetween).padding({ left: this.isPhone ? '2%' : '1%', right: this.isPhone ? '2%' : '1%' }).width(this.isPhone ? '57.2%' : '30%').aspectRatio(this.isPhone ? 5.15 : 7).backgroundColor('rgba(0,0,0,0.40)').borderRadius(this.isPhone ? 26 : 36)Column() {Image($r('app.media.live_share')).width(42).height(42)}.margin({ left: this.isPhone ? '12%' : '49%' })Column() {Image($r('app.media.live_close')).id('close').width(42).height(42).onClick(() => {router.back();})}.margin({ left: '4%' })}.position({ x: '2%', y: '5.1%' })// Column() {// CommentPage() // }// .position({ x: '2%', y: this.isPhone ? '72%' : '62%' })}.backgroundColor('#D8D8D8').width('100%').height('100%')})}.width('100%').height('100%').loop(false).indicator(false).vertical(true) //设置 Swiper 为垂直方向滑动,配合响应式布局,使得视频在不同屏幕方向下都能正确展示。.onChange((index: number) => {this.active = index;})}}}
}
二、多方案实现画中画功能开发
1. 使用XComponent实现画中画功能开发
- 这里我们直接拉了官方的代码,他导入的AVPlayerDemo其实就是我们上面的AVPlayer,我们给他实现一下
- ①
PiPWindow.create(config)
拿到控制器 - ② 通过画中画控制器实例的
tAutoStartEnabled
口设置是否需要在应用返回桌面时自动启动画中画。 - ③ 通过画中画控制器实例的on('stateChange')接口注册生命周期事件回调。
- ④ 通过画中画控制器实例的on('controlPanelActionEvent')接口注册控制事件回调。
- ⑤ 通过
startPiP()
口启动画中画。
// 该页面用于展示画中画功能的基本使用
//import { AVPlayerDemo } from './AVPlayerDemo'; // 请自行实现视频播放的相关开发
import { BuilderNode, FrameNode, NodeController, Size, UIContext, PiPWindow } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';
import { media } from '@kit.MediaKit';
import { MkNavbar } from 'basic';const TAG = 'PIPView';class Params {text: string = '';constructor(text: string) {this.text = text;}
}// 开发者可以通过@Builder装饰器实现布局构建
@Builder
function buildText(params: Params) {Column() {Text(params.text).fontSize(20).fontColor(Color.Red)}.width('100%') // 宽度方向充满画中画窗口.height('100%') // 高度方向充满画中画窗口
}// 开发者可通过继承NodeController实现自定义UI控制器
class TextNodeController extends NodeController {private message: string;private textNode: BuilderNode<[Params]> | null = null;constructor(message: string) {super();this.message = message;}// 通过BuilderNode加载自定义布局makeNode(context: UIContext): FrameNode | null {this.textNode = new BuilderNode(context);this.textNode.build(wrapBuilder<[Params]>(buildText), new Params(this.message));return this.textNode.getFrameNode();}// 开发者可自定义该方法实现布局更新update(message: string) {console.log(`update message: ${message}`);if (this.textNode !== null) {this.textNode.update(new Params(message));}}
}@Builder
function PIPViewBuilder() {PIPView()
}@Component
export struct PIPView {@Consume pageStack: NavPathStackprivate surfaceId: string = ''; // surfaceId,用于关联XComponent与视频播放器private mXComponentController: XComponentController = new XComponentController();//private player?: AVPlayerDemo = undefined;private pipController?: PiPWindow.PiPController = undefined;private nodeController: TextNodeController = new TextNodeController('你看我屌吗');navId: string = '';avPlayer: media.AVPlayer = Object()url: string = "https://vdept3.bdstatic.com/mda-rb9vxh2uiq5jq71p/cae_h264/1739221710340328239/mda-rb9vxh2uiq5jq71p.mp4?v_from_s=hkapp-haokan-nanjing&auth_key=1743309662-0-0-33813516e090597e2933bb263907310a&bcevod_channel=searchbox_feed&cr=0&cd=0&pd=1&pt=3&logid=2462875327&vid=8058861658488074906&klogid=2462875327&abtest="//"https://vdept3.bdstatic.com/mda-rb9vxh2uiq5jq71p/cae_h264/1739221710340328239/mda-rb9vxh2uiq5jq71p.mp4?v_from_s=hkapp-haokan-nanjing&auth_key=1743244119-0-0-7e704e3b242ea6b12d1825943add20cb&bcevod_channel=searchbox_feed&cr=0&cd=0&pd=1&pt=3&logid=1719267439&vid=8058861658488074906&klogid=1719267439&abtest="async avPlay(id:string){let avPlayer = await media.createAVPlayer()this.avPlayer = avPlayeravPlayer.on('stateChange',(state)=>{if (state == 'initialized') {avPlayer.surfaceId = idavPlayer.prepare()}if (state == 'prepared') {avPlayer.loop = trueavPlayer.play()}})avPlayer.url = this.url}build() {NavDestination() {Column() {MkNavbar({title: '画中画',leftClickHandler: () => {// 点击返回this.pageStack.pop()}})// XComponent控件,用于播放视频流XComponent({ id: 'pipDemo', type: 'surface', controller: this.mXComponentController }).onLoad(() => {this.surfaceId = this.mXComponentController.getXComponentSurfaceId();// 需要设置AVPlayer的surfaceId为XComponentController的surfaceIdthis.avPlay(this.surfaceId)//this.player = new AVPlayerDemo(this.surfaceId);//this.player.avPlayerFdSrcDemo();}).onDestroy(() => {console.info(`[${TAG}] XComponent onDestroy`);}).size({ width: '100%', height: '800px' })Row({ space: 20 }) {Button('start') // 启动画中画.onClick(() => {this.startPip();}).stateStyles({pressed: {.backgroundColor(Color.Red);},normal: {.backgroundColor(Color.Blue);}})Button('stop') // 停止画中画.onClick(() => {this.stopPip();this.avPlayer.stop()this.avPlayer.release()}).stateStyles({pressed: {.backgroundColor(Color.Red);},normal: {.backgroundColor(Color.Blue);}})Button('updateSize') // 更新视频尺寸.onClick(() => {// 此处设置的宽高应为媒体内容宽高,需要通过媒体相关接口或回调获取// 例如使用AVPlayer播放视频时,可通过videoSizeChange回调获取媒体源更新后的尺寸this.updateContentSize(900, 1600);}).stateStyles({pressed: {.backgroundColor(Color.Red);},normal: {.backgroundColor(Color.Blue);}})}.size({ width: '100%', height: 60 }).justifyContent(FlexAlign.SpaceAround)}.justifyContent(FlexAlign.Center).height('100%').width('100%')}.hideTitleBar(true)}startPip() {if (!PiPWindow.isPiPEnabled()) {console.error(`picture in picture disabled for current OS`);return;}let config: PiPWindow.PiPConfiguration = {context: getContext(this),componentController: this.mXComponentController,// 当前page导航id// 1、UIAbility使用Navigation管理页面,需要设置Navigation控件的id属性,并将该id设置给画中画控制器,确保还原场景下能够从画中画窗口恢复到原页面// 2、UIAbility使用Router管理页面时(画中画场景不推荐该导航方式),无需设置navigationId。注意:该场景下启动画中画后,不要进行页面切换,否则还原场景可能出现异常// 3、UIAbility只有单页面时,无需设置navigationId,还原场景下也能够从画中画窗口恢复到原页面navigationId: this.navId,templateType: PiPWindow.PiPTemplateType.VIDEO_PLAY, // 对于视频通话、视频会议等场景,需要设置相应的模板类型contentWidth: 1920, // 可选,创建画中画控制器时系统可通过XComponent组件大小设置画中画窗口比例contentHeight: 1080, // 可选,创建画中画控制器时系统可通过XComponent组件大小设置画中画窗口比例controlGroups:[PiPWindow.VideoPlayControlGroup.VIDEO_PREVIOUS_NEXT], // 可选,对于视频通话、视频会议和视频直播场景,可通过该属性选择对应模板类型下需显示的的控件组customUIController: this.nodeController, // 可选,如果需要在画中画显示内容上方展示自定义UI,可设置该参数。};// 步骤1:创建画中画控制器,通过create接口创建画中画控制器实例let promise : Promise<PiPWindow.PiPController> = PiPWindow.create(config);promise.then((controller : PiPWindow.PiPController) => {this.pipController = controller;// 步骤1:初始化画中画控制器this.initPipController();// 步骤2:通过startPiP接口启动画中画this.pipController.startPiP().then(() => {console.info(`Succeeded in starting pip.`);}).catch((err: BusinessError) => {console.error(`Failed to start pip. Cause:${err.code}, message:${err.message}`);});}).catch((err: BusinessError) => {console.error(`Failed to create pip controller. Cause:${err.code}, message:${err.message}`);});}initPipController() {if (!this.pipController) {return;}// 步骤1:通过setAutoStartEnabled接口设置是否需要在应用返回桌面时自动启动画中画,注册stateChange和controlPanelActionEvent回调this.pipController.setAutoStartEnabled(false /*or true if necessary*/); // 默认为falsethis.pipController.on('stateChange', (state: PiPWindow.PiPState, reason: string) => {this.onStateChange(state, reason);});this.pipController.on('controlPanelActionEvent', (event: PiPWindow.PiPActionEventType, status?: number) => {this.onActionEvent(event, status);});}onStateChange(state: PiPWindow.PiPState, reason: string) {let curState: string = '';switch(state) {case PiPWindow.PiPState.ABOUT_TO_START:curState = "ABOUT_TO_START";break;case PiPWindow.PiPState.STARTED:curState = "STARTED";break;case PiPWindow.PiPState.ABOUT_TO_STOP:curState = "ABOUT_TO_STOP";break;case PiPWindow.PiPState.STOPPED:curState = "STOPPED";break;case PiPWindow.PiPState.ABOUT_TO_RESTORE:curState = "ABOUT_TO_RESTORE";break;case PiPWindow.PiPState.ERROR:curState = "ERROR";break;default:break;}console.info(`[${TAG}] onStateChange: ${curState}, reason: ${reason}`);}onActionEvent(event: PiPWindow.PiPActionEventType, status?: number) {switch (event) {case 'playbackStateChanged':// 开始或停止视频if (status === 0) {// 停止视频} else if (status === 1) {// 播放视频}break;case 'nextVideo':// 播放上一个视频break;case 'previousVideo':// 播放下一个视频break;default:break;}}// 步骤3:视频内容变化时,向画中画控制器更新视频尺寸信息,用于调整画中画窗口比例updateContentSize(width: number, height: number) {if (this.pipController) {this.pipController.updateContentSize(width, height);}}// 步骤4:当不再需要显示画中画时,通过stopPiP接口关闭画中画stopPip() {if (this.pipController) {let promise : Promise<void> = this.pipController.stopPiP();promise.then(() => {console.info(`Succeeded in stopping pip.`);this.pipController?.off('stateChange'); // 如果已注册stateChange回调,停止画中画时取消注册该回调this.pipController?.off('controlPanelActionEvent'); // 如果已注册controlPanelActionEvent回调,停止画中画时取消注册该回调}).catch((err: BusinessError) => {console.error(`Failed to stop pip. Cause:${err.code}, message:${err.message}`);});}}
}
2. 用typeNode实现画中画功能开发
- 在使用create接口创建画中画时,可通过在PiPConfiguration中新增PiPControlGroup类型的数组配置当前画中画控制层控件。
- 比如
VideoCallControlGroup
视频通话控件组枚举。然后配置PiPTemplateType
为VIDEO_CALL
我们先封装一个PipManager(管理器)
// model/PipManager.ets
import { PiPWindow, typeNode } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';
import { XCNodeController } from './XCNodeController';
import { AVPlayer } from './AVPlayer'export class CustomXComponentController extends XComponentController {onSurfaceCreated(surfaceId: string): void {console.log(TAG, `onSurfaceCreated surfaceId: ${surfaceId}`);if (PipManager.getInstance().player.surfaceID === surfaceId) {return;}// 将surfaceId设置给媒体源PipManager.getInstance().player.surfaceID = surfaceId;PipManager.getInstance().player.avPlayerFdSrc();}onSurfaceDestroyed(surfaceId: string): void {console.log(TAG, `onSurfaceDestroyed surfaceId: ${surfaceId}`);}
}const TAG = 'PipManager';export class PipManager {private static instance: PipManager = new PipManager();private pipController?: PiPWindow.PiPController = undefined;private xcNodeController: XCNodeController;private mXComponentController: XComponentController;private lifeCycleCallback: Set<Function> = new Set();player: AVPlayer;public static getInstance(): PipManager {return PipManager.instance;}constructor() {this.xcNodeController = new XCNodeController();this.player = new AVPlayer();this.mXComponentController = new CustomXComponentController();}public registerLifecycleCallback(callBack: Function) {this.lifeCycleCallback.add(callBack);}public unRegisterLifecycleCallback(callBack: Function): void {this.lifeCycleCallback.delete(callBack);}getNode(): typeNode.XComponent | null {return this.xcNodeController.getNode();}onActionEvent(control: PiPWindow.ControlEventParam) {switch (control.controlType) {case PiPWindow.PiPControlType.VIDEO_PLAY_PAUSE:if (control.status === PiPWindow.PiPControlStatus.PAUSE) {//停止视频} else if (control.status === PiPWindow.PiPControlStatus.PLAY) {//播放视频}break;case PiPWindow.PiPControlType.VIDEO_NEXT:// 切换到下一个视频break;case PiPWindow.PiPControlType.VIDEO_PREVIOUS:// 切换到上一个视频break;case PiPWindow.PiPControlType.FAST_FORWARD:// 视频进度快进break;case PiPWindow.PiPControlType.FAST_BACKWARD:// 视频进度后退break;default:break;}console.info('onActionEvent, controlType:' + control.controlType + ', status' + control.status);}onStateChange(state: PiPWindow.PiPState, reason: string) {let curState: string = '';this.xcNodeController.setCanAddNode(state === PiPWindow.PiPState.ABOUT_TO_STOP || state === PiPWindow.PiPState.STOPPED)if (this.lifeCycleCallback !== null) {this.lifeCycleCallback.forEach((fun) => {fun(state);});}switch (state) {case PiPWindow.PiPState.ABOUT_TO_START:curState = "ABOUT_TO_START";// 将typeNode节点从布局移除this.xcNodeController.removeNode();break;case PiPWindow.PiPState.STARTED:curState = "STARTED";break;case PiPWindow.PiPState.ABOUT_TO_STOP:curState = "ABOUT_TO_STOP";break;case PiPWindow.PiPState.STOPPED:curState = "STOPPED";break;case PiPWindow.PiPState.ABOUT_TO_RESTORE:curState = "ABOUT_TO_RESTORE";break;case PiPWindow.PiPState.ERROR:curState = "ERROR";break;default:break;}console.info(`[${TAG}] onStateChange: ${curState}, reason: ${reason}`);}unregisterPipStateChangeListener() {console.info(`${TAG} aboutToDisappear`);this.pipController?.off('stateChange');this.pipController?.off('controlEvent');}getXComponentController(): CustomXComponentController {return this.mXComponentController;}// 步骤1:创建画中画控制器,注册生命周期事件以及控制事件回调init(ctx: Context) {if (this.pipController !== null && this.pipController != undefined) {return;}console.info(`${TAG} onPageShow`)if (!PiPWindow.isPiPEnabled()) {console.error(TAG, `picture in picture disabled for current OS`);return;}let config: PiPWindow.PiPConfiguration = {context: ctx,componentController: this.getXComponentController(),templateType: PiPWindow.PiPTemplateType.VIDEO_PLAY,contentWidth: 1920, // 使用typeNode启动画中画时,contentWidth需设置为大于0的值,否则创建画中画失败contentHeight: 1080, // 使用typeNode启动画中画时,contentHeight需设置为大于0的值,否则创建画中画失败};// 通过create接口创建画中画控制器实例let promise: Promise<PiPWindow.PiPController> = PiPWindow.create(config, this.xcNodeController.getNode());promise.then((controller: PiPWindow.PiPController) => {this.pipController = controller;// 通过画中画控制器实例的setAutoStartEnabled接口设置是否需要在应用返回桌面时自动启动画中画this.pipController?.setAutoStartEnabled(true);// 通过画中画控制器实例的on('stateChange')接口注册生命周期事件回调this.pipController.on('stateChange', (state: PiPWindow.PiPState, reason: string) => {this.onStateChange(state, reason);});// 通过画中画控制器实例的on('controlEvent')接口注册控制事件回调this.pipController.on('controlEvent', (control: PiPWindow.ControlEventParam) => {this.onActionEvent(control);});}).catch((err: BusinessError) => {console.error(TAG, `Failed to create pip controller. Cause:${err.code}, message:${err.message}`);});}// 步骤2:启动画中画startPip() {this.pipController?.startPiP().then(() => {console.info(TAG, `Succeeded in starting pip.`);}).catch((err: BusinessError) => {console.error(TAG, `Failed to start pip. Cause:${err.code}, message:${err.message}`);});}// 步骤3:更新媒体源尺寸信息updateContentSize(width: number, height: number) {if (this.pipController) {this.pipController.updateContentSize(width, height);}}// 步骤4:关闭画中画stopPip() {if (this.pipController === null || this.pipController === undefined) {return;}let promise: Promise<void> = this.pipController.stopPiP();promise.then(() => {console.info(TAG, `Succeeded in stopping pip.`);}).catch((err: BusinessError) => {console.error(TAG, `Failed to stop pip. Cause:${err.code}, message:${err.message}`);});}getNodeController(): XCNodeController {console.info(TAG, `getNodeController.`);return this.xcNodeController;}setAutoStart(autoStart: boolean): void {this.pipController?.setAutoStartEnabled(autoStart);}removeNode() {this.xcNodeController.removeNode();}addNode(): void {this.xcNodeController.addNode();}
}
配置页面
// pages/Page1.ets
import { PipManager } from '../model/PipManager';const TAG = 'Page1';@Component
export struct Page1 {build() {Column() {Text('This is Page1').fontSize(30).fontWeight(FontWeight.Bold).margin({bottom: 20})// 将typeNode添加到页面布局中NodeContainer(PipManager.getInstance().getNodeController()).size({ width: '100%', height: '800px' })Row({ space: 20 }) {Button('startPip')// 启动画中画.onClick(() => {PipManager.getInstance().startPip();})Button('stopPip')// 停止画中画.onClick(() => {PipManager.getInstance().stopPip();})Button('updateSize')// 更新视频尺寸.onClick(() => {// 此处设置的宽高应为媒体内容宽高,需要通过媒体相关接口或回调获取// 例如使用AVPlayer播放视频时,可通过videoSizeChange回调获取媒体源更新后的尺寸PipManager.getInstance().updateContentSize(900, 1600);})}.backgroundColor('#4da99797').size({ width: '100%', height: 60 }).justifyContent(FlexAlign.SpaceAround)}.justifyContent(FlexAlign.Center).width('100%').height('100%')}onPageShow(): void {console.info(TAG, 'onPageShow')PipManager.getInstance().initPipController(getContext(this));PipManager.getInstance().setAutoStart(true);}onPageHide(): void {console.info(TAG, 'onPageHide')PipManager.getInstance().setAutoStart(false);PipManager.getInstance().removeNode();}
}