文档管理中心
您当前正在浏览新版开发者文档中心,目录分类和层级有所调整。点击左侧当前文档分类名称前的“☰”图标,可切换文档分类。 了解新版目录
指南与API参考指南应用框架ArkUI(方舟UI框架)窗口管理窗口类型画中画开发指导使用typeNode实现画中画功能开发 (ArkTS)

使用typeNode实现画中画功能开发 (ArkTS)

说明
  • 从API version 12开始,支持使用typeNode实现画中画功能开发。

该方式适用于任意需要接入画中画功能的场景。下文将结合实际开发场景,通过示例说明各场景下的实现步骤,以及相关场景中typeNode与页面的管理方法。

约束与限制

  • 构造PiPConfiguration参数时,建议传入contentWidth和contentHeight参数用以计算画中画初始宽高比例,否则系统将以16:9的宽高比例呈现画中画窗口。

  • contentNode支持XComponentType.SURFACE类型,且创建typeNode时必须指定为"XComponent"类型。

  • 在关闭画中画时,需要检查自定义组件节点是否释放,避免出现内存泄漏。

  • 设备限制:支持在Phone、Tablet、PC/2in1、TV、Car设备使用typeNode实现画中画。

开发步骤

本文以视频播放为例,介绍通过typeNode实现画中画功能的基本开发步骤。

示例中的视频播放器简易实现参考:

收起
自动换行
深色代码主题
复制
  1. // model/AVPlayer.ets
  2. // 简易播放器实现
  3. import { BusinessError } from '@kit.BasicServicesKit';
  4. import { common } from '@kit.AbilityKit';
  5. import { media } from '@kit.MediaKit';
  6. import { Logger } from '../util/LogUtil';
  7. export class AVPlayer {
  8. private avPlayer?: media.AVPlayer;
  9. public surfaceID: string = '';
  10. setAVPlayerCallback() {
  11. this.avPlayer?.on('seekDone', (seekDoneTime: number) => {
  12. Logger.info(`AVPlayer seek succeeded, seek time is ${seekDoneTime}`);
  13. })
  14. this.avPlayer?.on('stateChange', async (state, reason) => {
  15. if (!this.avPlayer) {
  16. return;
  17. }
  18. switch (state) {
  19. case 'idle':
  20. this.avPlayer.release();
  21. break;
  22. case 'initialized':
  23. this.avPlayer.surfaceId = this.surfaceID;
  24. this.avPlayer.prepare().then(() => {
  25. Logger.info('AVPlayer prepare succeeded.');
  26. }, (err: BusinessError) => {
  27. Logger.error(`Invoke prepare failed, code is ${err.code}, message is ${err.message}`);
  28. });
  29. break;
  30. case 'prepared':
  31. this.avPlayer.play();
  32. break;
  33. case 'stopped':
  34. this.avPlayer.reset();
  35. break;
  36. default:
  37. break;
  38. }
  39. })
  40. }
  41. async avPlayerFdSrc() {
  42. try {
  43. this.avPlayer = await media.createAVPlayer();
  44. } catch(err) {
  45. Logger.error(`create AVPlayer failed`);
  46. };
  47. this.setAVPlayerCallback();
  48. let uiContext = AppStorage.get('UIContext') as UIContext;
  49. let context = uiContext.getHostContext() as common.UIAbilityContext;
  50. try {
  51. let fileDescriptor = await context.resourceManager.getRawFd('xxx.mp4');
  52. if (this.avPlayer) {
  53. this.avPlayer.fdSrc = fileDescriptor;
  54. }
  55. } catch (err) {
  56. console.error(`AVPlayer error: ${JSON.stringify(err)}`)
  57. }
  58. }
  59. }
收起
自动换行
深色代码主题
复制
  1. export class CustomXComponentController extends XComponentController {
  2. onSurfaceCreated(surfaceId: string): void {
  3. Logger.info(TAG, `onSurfaceCreated surfaceId: ${surfaceId}`);
  4. if (PipManager.getInstance().player.surfaceID === surfaceId) {
  5. return;
  6. }
  7. // XComponent创建Surface,通过onSurfaceCreated回调获取surfaceId
  8. // 将surfaceId设置给AVPlayer,建立XComponent与AVPlayer的关联
  9. // AVPlayer会将视频画面渲染到XComponent的Surface中
  10. PipManager.getInstance().player.surfaceID = surfaceId;
  11. PipManager.getInstance().player.avPlayerFdSrc();
  12. }
  13. onSurfaceDestroyed(surfaceId: string): void {
  14. Logger.info(TAG, `onSurfaceDestroyed surfaceId: ${surfaceId}`);
  15. }
  16. }
  17. export class PipManager {
  18. // ...
  19. private mXComponentController: XComponentController;
  20. // ...
  21. constructor() {
  22. this.xcNodeController = new XCNodeController();
  23. this.player = new AVPlayer();
  24. this.mXComponentController = new CustomXComponentController();
  25. }
  26. // ...
  27. }

如上,将XComponentController与视频关联,用于后续创建参数的配置。

  1. 创建typeNode节点,typeNode可以选择是否添加到布局中。

    • typeNode作为自由节点,不添加到布局中。适用于简单的画中画场景,节点只在画中画中显示,无需额外的管理。

      通过主窗口获取UIContext,然后使用typeNode.createNode接口创建typeNode节点。

      收起
      自动换行
      深色代码主题
      复制
      1. export default class EntryAbility extends UIAbility {
      2. // ...
      3. onWindowStageCreate(windowStage: window.WindowStage): void {
      4. // ...
      5. windowStage.getMainWindow().then((window) => {
      6. // ...
      7. let ctx = window.getUIContext();
      8. AppStorage.setOrCreate('UIContext', ctx);
      9. // 通过主窗口UIContext创建typeNode节点
      10. PipManager.getInstance().makeTypeNode(ctx);
      收起
      自动换行
      深色代码主题
      复制
      1. makeTypeNode(ctx: UIContext): void {
      2. if (this.xComponent === null || this.xComponent === undefined) {
      3. this.xComponent = typeNode.createNode(ctx, 'XComponent', {
      4. type: XComponentType.SURFACE,
      5. controller: this.getXComponentController(),
      6. });
      7. }
      8. }
    • 添加typeNode节点到布局中。适用于在主页预览视频,然后切换到画中画的场景。需要结合画中画的生命周期来管理视频节点的添加和移除,并根据应用使用的导航方式(单界面Ability、Router导航、Navigation导航)处理页面跳转,从而实现主页面和画中画之间的灵活切换。

      创建自定义NodeController,实现makeNode方法,在该方法中创建typeNode。

      收起
      自动换行
      深色代码主题
      复制
      1. export class XCNodeController extends NodeController {
      2. public xComponent: typeNode.XComponent | null = null;
      3. private node: FrameNode | null = null;
      4. private canAddNode: boolean = true;
      5. setCanAddNode(canAddNode: boolean): void {
      6. this.canAddNode = canAddNode;
      7. }
      8. makeNode(context: UIContext): FrameNode | null {
      9. Logger.info(TAG, 'makeNode');
      10. this.node = new FrameNode(context);
      11. if (this.xComponent === null || this.xComponent === undefined) {
      12. this.xComponent = typeNode.createNode(context, 'XComponent', {
      13. type: XComponentType.SURFACE,
      14. controller: PipManager.getInstance().getXComponentController(),
      15. });
      16. }
      17. if (this.canAddNode) {
      18. try {
      19. this.xComponent.getParent()?.removeChild(this.xComponent);
      20. } catch (error) {
      21. Logger.error(TAG, 'Failed to removeChild');
      22. }
      23. try {
      24. this.node.appendChild(this.xComponent);
      25. } catch (error) {
      26. Logger.error(TAG, 'Failed to appendChild');
      27. }
      28. }
      29. return this.node;
      30. }

      通过NodeContainer将typeNode添加到页面布局中。

      收起
      自动换行
      深色代码主题
      复制
      1. @Component
      2. export struct Page1 {
      3. build() {
      4. NavDestination() {
      5. Column() {
      6. // ...
      7. // 将typeNode添加到页面布局中
      8. NodeContainer(PipManager.getInstance().getNodeController())
      9. .size({ width: '100%', height: '800px' })
      10. // ...
      11. }
      12. // ...
      13. }
      14. // ...
      15. }
      16. }
  2. 设置画中画配置参数。

    • 通过在PiPConfiguration中新增PiPControlGroup类型的数组配置当前画中画控制层控件。

      收起
      自动换行
      深色代码主题
      复制
      1. function getControlGroups(templateType: PiPWindow.PiPTemplateType): PiPControlGroups {
      2. switch (templateType) {
      3. case PiPWindow.PiPTemplateType.VIDEO_PLAY:
      4. return [PiPWindow.VideoPlayControlGroup.VIDEO_PREVIOUS_NEXT];
      5. case PiPWindow.PiPTemplateType.VIDEO_CALL:
      6. return [PiPWindow.VideoCallControlGroup.MICROPHONE_SWITCH,
      7. PiPWindow.VideoCallControlGroup.HANG_UP_BUTTON, PiPWindow.VideoCallControlGroup.CAMERA_SWITCH];
      8. case PiPWindow.PiPTemplateType.VIDEO_MEETING:
      9. return [PiPWindow.VideoMeetingControlGroup.MICROPHONE_SWITCH,
      10. PiPWindow.VideoMeetingControlGroup.HANG_UP_BUTTON, PiPWindow.VideoMeetingControlGroup.CAMERA_SWITCH];
      11. case PiPWindow.PiPTemplateType.VIDEO_LIVE:
      12. return [PiPWindow.VideoLiveControlGroup.VIDEO_PLAY_PAUSE,
      13. PiPWindow.VideoLiveControlGroup.MUTE_SWITCH];
      14. default:
      15. return [];
      16. }
      17. }
      18. // ...
      19. @Component
      20. export struct Page1 {
      21. // ...
      22. @State currentTemplateType: PiPWindow.PiPTemplateType = PiPWindow.PiPTemplateType.VIDEO_PLAY;
      23. // ...
      24. startPip() {
      25. // ...
      26. let config: PiPWindow.PiPConfiguration = {
      27. // ...
      28. // 对于视频通话、视频会议等场景,需要设置相应的模板类型
      29. templateType: this.currentTemplateType,
      30. // 可选,对于视频通话、视频会议和视频直播场景,可通过该属性选择对应模板类型下需显示的控件组
      31. controlGroups: getControlGroups(this.currentTemplateType),
      32. // ...
      33. };
      34. // ...
      35. }
      36. // ...
      37. }
    • (可选)通过在PiPConfiguration中传入customUIController来显示自定义UI。

      • 创建自定义NodeController,实现makeNode方法,在该方法中加载自定义UI布局。
        收起
        自动换行
        深色代码主题
        复制
        1. // 开发者可通过继承NodeController实现自定义UI控制器
        2. class TextNodeController extends NodeController {
        3. private message: string;
        4. private textNode: BuilderNode<[Params]> | null = null;
        5. constructor(message: string) {
        6. super();
        7. this.message = message;
        8. }
        9. // 通过BuilderNode加载自定义布局
        10. makeNode(context: UIContext): FrameNode | null {
        11. this.textNode = new BuilderNode(context);
        12. this.textNode.build(wrapBuilder<[Params]>(buildText), new Params(this.message));
        13. return this.textNode.getFrameNode();
        14. }
        15. // 开发者可自定义该方法实现布局更新
        16. update(message: string) {
        17. Logger.info(`update message: ${message}`);
        18. if (this.textNode !== null) {
        19. this.textNode.update(new Params(message));
        20. }
        21. }
        22. // 开发者需要定义该方法实现布局的注销,避免内存泄漏
        23. dispose() {
        24. Logger.info('dispose message: execute node dispose');
        25. if (this.textNode !== null) {
        26. this.textNode.dispose();
        27. }
        28. }
        29. }
      • 通过装饰器实现布局构建。
        收起
        自动换行
        深色代码主题
        复制
        1. class Params {
        2. public text: string = '';
        3. constructor(text: string) {
        4. this.text = text;
        5. }
        6. }
        7. // 开发者可以通过@Builder装饰器实现布局构建
        8. @Builder
        9. function buildText(params: Params) {
        10. Column() {
        11. Text(params.text)
        12. .fontSize(20)
        13. .fontColor(Color.Red)
        14. }
        15. .width('100%') // 宽度方向充满画中画窗口
        16. .height('100%') // 高度方向充满画中画窗口
        17. }
      • 在创建画中画控制器时,将customUIController参数传入PiPConfiguration。
        收起
        自动换行
        深色代码主题
        复制
        1. @Component
        2. export struct Page1 {
        3. // ...
        4. private nodeController: TextNodeController = new TextNodeController('this is custom UI');
        5. // ...
        6. startPip() {
        7. // ...
        8. let config: PiPWindow.PiPConfiguration = {
        9. // ...
        10. // 可选,如果需要在画中画显示内容上方展示自定义UI,可设置该参数。
        11. customUIController: this.nodeController,
        12. };
        13. // ...
        14. }
        15. // ...
        16. }
  3. 创建画中画控制器,需要配置画中画的上下文环境、XComponent控制器(关联视频的surfaceId)、画中画模板类型、原始内容的宽高等参数。

    通过create(config: PiPConfiguration, contentNode: typeNode.XComponent)接口创建画中画控制器实例。

    收起
    自动换行
    深色代码主题
    复制
    1. init(ctx: Context): void {
    2. this.createPipController(ctx, this.getNode());
    3. }
    4. private createPipController(ctx: Context, node: typeNode.XComponent | null): void {
    5. if (this.pipController !== null && this.pipController !== undefined) {
    6. return;
    7. }
    8. Logger.info(`${TAG} onPageShow`);
    9. if (!PiPWindow.isPiPEnabled()) {
    10. Logger.error(TAG, `picture in picture disabled for current OS`);
    11. return;
    12. }
    13. const config: PiPWindow.PiPConfiguration = {
    14. context: ctx,
    15. componentController: this.getXComponentController(),
    16. templateType: PiPWindow.PiPTemplateType.VIDEO_PLAY,
    17. contentWidth: 1920,
    18. contentHeight: 1080,
    19. };
    20. PiPWindow.create(config, node).then((controller: PiPWindow.PiPController) => {
    21. // ...
    22. }).catch((err: BusinessError) => {
    23. Logger.error(TAG, `Failed to create pip controller. Cause:${err.code}, message:${err.message}`);
    24. });
    25. }
  4. 注册生命周期事件和控制事件回调。

    通过画中画控制器实例的on('stateChange')接口注册生命周期事件回调。通过画中画控制器实例的on('controlEvent')接口注册控制事件回调。

    收起
    自动换行
    深色代码主题
    复制
    1. PiPWindow.create(config, node).then((controller: PiPWindow.PiPController) => {
    2. this.pipController = controller;
    3. // ...
    4. this.pipController.on('stateChange', (state: PiPWindow.PiPState, reason: string) => {
    5. this.onStateChange(state, reason);
    6. });
    7. this.pipController.on('controlEvent', (control: PiPWindow.ControlEventParam) => {
    8. this.onActionEvent(control);
    9. });
    10. }).catch((err: BusinessError) => {
    11. Logger.error(TAG, `Failed to create pip controller. Cause:${err.code}, message:${err.message}`);
    12. });

    对于添加到布局中的typeNode节点,需要应用自行管理。不同的场景处理存在一定差异,主要体现在生命周期事件回调的处理上。

    • 应用使用单界面UIAbility。

      在画中画ABOUT_TO_START生命周期将typeNode节点从布局移除。

      收起
      自动换行
      深色代码主题
      复制
      1. onStateChange(state: PiPWindow.PiPState, reason: string): void {
      2. let curState: string = '';
      3. this.xcNodeController.setCanAddNode(
      4. state === PiPWindow.PiPState.ABOUT_TO_STOP || state === PiPWindow.PiPState.STOPPED);
      5. this.lifeCycleCallback.forEach((fun) => {
      6. fun(state);
      7. });
      8. switch (state) {
      9. case PiPWindow.PiPState.ABOUT_TO_START:
      10. curState = 'ABOUT_TO_START';
      11. this.xcNodeController.removeNode();
      12. break;
      13. // ...
      14. }
      15. Logger.info(`[${TAG}] onStateChange: ${curState}, reason: ${reason}`);
      16. }

      可根据业务需要,在画中画ABOUT_TO_STOP生命周期时将typeNode节点重新添加到布局中。

      收起
      自动换行
      深色代码主题
      复制
      1. @Entry
      2. @Component
      3. struct AbilityImplementPage {
      4. private callback: Function = (state: PiPWindow.PiPState) => {
      5. if (state === PiPWindow.PiPState.ABOUT_TO_STOP) {
      6. // 画中画关闭或还原时触发ABOUT_TO_STOP生命周期,此时需要重新添加节点
      7. Logger.info(`${TAG}, ABOUT_TO_STOP`)
      8. PipManager.getInstance().addNode();
      9. }
      10. };
      11. build() {
      12. Column() {
      13. Text('This is MainPage')
      14. .fontSize(30)
      15. .fontWeight(FontWeight.Bold)
      16. .margin({ bottom: 20 })
      17. // 将typeNode添加到页面布局中
      18. NodeContainer(PipManager.getInstance().getNodeController())
      19. .size({ width: '100%', height: '800px' })
      20. Row({ space: 20 }) {
      21. Button('startPip') // 启动画中画
      22. .onClick(() => {
      23. PipManager.getInstance().startPip();
      24. })
      25. Button('stopPip') // 停止画中画
      26. .onClick(() => {
      27. PipManager.getInstance().stopPip();
      28. })
      29. Button('updateSize') // 更新视频尺寸
      30. .onClick(() => {
      31. // 此处设置的宽高应为媒体内容宽高,需要通过媒体相关接口或回调获取
      32. // 例如使用AVPlayer播放视频时,可通过videoSizeChange回调获取媒体源更新后的尺寸
      33. PipManager.getInstance().updateContentSize(900, 1600);
      34. })
      35. }
      36. .backgroundColor('#4da99797')
      37. .size({ width: '100%', height: 60 })
      38. .justifyContent(FlexAlign.SpaceAround)
      39. }
      40. .justifyContent(FlexAlign.Center)
      41. .width('100%')
      42. .height('100%')
      43. }
      44. aboutToAppear(): void {
      45. PipManager.getInstance().registerLifecycleCallback(this.callback);
      46. }
      47. // ...
      48. }
    • 应用使用Router导航。

      在画中画ABOUT_TO_START生命周期将typeNode节点从布局移除。

      收起
      自动换行
      深色代码主题
      复制
      1. onStateChange(state: PiPWindow.PiPState, reason: string): void {
      2. let curState: string = '';
      3. this.xcNodeController.setCanAddNode(
      4. state === PiPWindow.PiPState.ABOUT_TO_STOP || state === PiPWindow.PiPState.STOPPED);
      5. this.lifeCycleCallback.forEach((fun) => {
      6. fun(state);
      7. });
      8. switch (state) {
      9. case PiPWindow.PiPState.ABOUT_TO_START:
      10. curState = 'ABOUT_TO_START';
      11. this.xcNodeController.removeNode();
      12. break;
      13. // ...
      14. }
      15. Logger.info(`[${TAG}] onStateChange: ${curState}, reason: ${reason}`);
      16. }

      在画中画ABOUT_TO_START生命周期返回上级界面(可选)。如果启动画中画时返回了上级界面,需要在画中画ABOUT_TO_RESTORE(还原)时重新跳转到原界面。

      收起
      自动换行
      深色代码主题
      复制
      1. @Entry
      2. @Component
      3. struct RouterImplementPage {
      4. private page1: string = 'route/Page1';
      5. private pageRouter: Router | null = null;
      6. // 画中画生命周期事件监听,用于页面及节点操作
      7. private callback: Function = (state: PiPWindow.PiPState) => {
      8. Logger.info(TAG, `pipStateChange: state ${state}`);
      9. if (state === PiPWindow.PiPState.ABOUT_TO_START) {
      10. // 返回到上级页面(可选)
      11. this.pageRouter?.back();
      12. } else if (state === PiPWindow.PiPState.ABOUT_TO_STOP) {
      13. // 画中画关闭或还原时触发ABOUT_TO_STOP生命周期,重新将typeNode节点添加到布局中
      14. PipManager.getInstance().addNode();
      15. } else if (state === PiPWindow.PiPState.ABOUT_TO_RESTORE) {
      16. // 如果在ABOUT_TO_START时返回了上级界面,需要还原时push到原界面
      17. this.jumpNext();
      18. }
      19. };
      20. aboutToAppear(): void {
      21. this.pageRouter = this.getUIContext().getRouter();
      22. PipManager.getInstance().registerLifecycleCallback(this.callback);
      23. }
      24. // ...
      25. jumpNext(): void {
      26. let topPage = this.pageRouter?.getState();
      27. if (topPage !== undefined && (this.page1.toString() === topPage.path + topPage.name)) {
      28. Logger.info(TAG, `page1 already at top`)
      29. return;
      30. }
      31. this.pageRouter?.pushUrl({
      32. url: this.page1 // 目标url
      33. }, router.RouterMode.Standard, (err) => {
      34. if (err) {
      35. Logger.error(TAG, `Invoke pushUrl failed, code is ${err.code}: ${err.message}`);
      36. return;
      37. }
      38. Logger.info(TAG, 'Invoke pushUrl succeeded.');
      39. });
      40. }
      41. // ...
      42. }
    • 应用使用Navigation导航。

      在画中画ABOUT_TO_START生命周期将typeNode节点从布局移除。

      收起
      自动换行
      深色代码主题
      复制
      1. onStateChange(state: PiPWindow.PiPState, reason: string): void {
      2. let curState: string = '';
      3. this.xcNodeController.setCanAddNode(
      4. state === PiPWindow.PiPState.ABOUT_TO_STOP || state === PiPWindow.PiPState.STOPPED);
      5. this.lifeCycleCallback.forEach((fun) => {
      6. fun(state);
      7. });
      8. switch (state) {
      9. case PiPWindow.PiPState.ABOUT_TO_START:
      10. curState = 'ABOUT_TO_START';
      11. this.xcNodeController.removeNode();
      12. break;
      13. // ...
      14. }
      15. Logger.info(`[${TAG}] onStateChange: ${curState}, reason: ${reason}`);
      16. }

      在画中画ABOUT_TO_START生命周期返回上级界面(可选)。如果启动画中画时返回了上级界面,需要在画中画ABOUT_TO_RESTORE(还原)时重新跳转到原界面。

      收起
      自动换行
      深色代码主题
      复制
      1. @Entry
      2. @Component
      3. struct NavigationImplementPage {
      4. @Provide('pageInfos') pageInfos: NavPathStack = new NavPathStack();
      5. // 画中画生命周期事件监听,用于页面及节点操作
      6. private callback: Function = (state: PiPWindow.PiPState) => {
      7. Logger.info(TAG, `pipStateChange: state ${state}`);
      8. if (state === PiPWindow.PiPState.ABOUT_TO_START) {
      9. // 返回到上级页面(可选)
      10. this.pageInfos.pop();
      11. } else if (state === PiPWindow.PiPState.ABOUT_TO_STOP) {
      12. // 画中画关闭或还原时触发ABOUT_TO_STOP生命周期,重新将typeNode节点添加到布局中
      13. PipManager.getInstance().addNode();
      14. } else if (state === PiPWindow.PiPState.ABOUT_TO_RESTORE) {
      15. // 如果在ABOUT_TO_START时返回了上级界面,需要还原时push到原界面
      16. this.jumpNext();
      17. }
      18. };
      19. jumpNext() {
      20. if (this.pageInfos.getAllPathName()[0] === 'Page1') {
      21. Logger.info(TAG, 'Page1 already at top');
      22. return;
      23. }
      24. this.pageInfos.pushPath({ name: 'Page1' });
      25. }
      26. aboutToAppear(): void {
      27. PipManager.getInstance().registerLifecycleCallback(this.callback);
      28. }
      29. // ...
      30. @Builder
      31. PageMap(name: string) {
      32. if (name === 'Page1') {
      33. Page1();
      34. }
      35. }
      36. build() {
      37. Navigation(this.pageInfos) {
      38. // ...
      39. }
      40. .title('MainTitle')
      41. .navDestination(this.PageMap)
      42. }
      43. }
  5. 启动画中画。

    • 创建画中画控制器实例后,通过startPiP接口启动画中画。

      收起
      自动换行
      深色代码主题
      复制
      1. startPip(): void {
      2. this.pipController?.startPiP().then(() => {
      3. Logger.info(TAG, `Succeeded in starting pip.`);
      4. }).catch((err: BusinessError) => {
      5. Logger.error(TAG, `Failed to start pip. Cause:${err.code}, message:${err.message}`);
      6. });
      7. }
    • 通过画中画控制器实例的setAutoStartEnabled接口设置在拉起画中画的应用主窗退后台时是否自动启动画中画,默认不自动拉起。在开启自动拉起的情况下,当应用主窗为智慧多窗悬浮窗状态且被收入侧边栏时,应用主窗虽退后台,但不会自动拉起画中画。

      收起
      自动换行
      深色代码主题
      复制
      1. PiPWindow.create(config, node).then((controller: PiPWindow.PiPController) => {
      2. this.pipController = controller;
      3. this.pipController.setAutoStartEnabled(true);
      4. // ...
      5. }).catch((err: BusinessError) => {
      6. Logger.error(TAG, `Failed to create pip controller. Cause:${err.code}, message:${err.message}`);
      7. });
  6. 更新媒体源尺寸信息。

    画中画媒体源更新后(如切换视频),通过画中画控制器实例的updateContentSize接口更新媒体源尺寸信息,以调整画中画窗口比例。

    收起
    自动换行
    深色代码主题
    复制
    1. updateContentSize(width: number, height: number): void {
    2. if (this.pipController) {
    3. this.pipController.updateContentSize(width, height);
    4. }
    5. }
  7. 关闭画中画。

    当不再需要显示画中画时,可根据业务需要,通过画中画控制器实例的stopPiP接口关闭画中画。

    收起
    自动换行
    深色代码主题
    复制
    1. stopPip(): void {
    2. if (this.pipController === null || this.pipController === undefined) {
    3. return;
    4. }
    5. this.pipController.stopPiP()
    6. .then(() => {
    7. Logger.info(TAG, `Succeeded in stopping pip.`);
    8. }).catch((err: BusinessError) => {
    9. Logger.error(TAG, `Failed to stop pip. Cause:${err.code}, message:${err.message}`);
    10. });
    11. }

应用使用typeNode自由节点(不添加到布局)实现画中画功能示例代码对应的示意图如下所示:

应用将typeNode 添加到布局中(使用Router导航、Navigation导航、单界面Ability)实现画中画功能示例代码对应的示意图如下所示: