鸿蒙应用框架开发【画中画效果实现】 UI框架

news2024/11/15 8:30:39

画中画效果实现

介绍

本示例通过@kit.ArkUI、@kit.MediaKit等接口,实现了视频播放、手动和自动拉起画中画、画中画窗口控制视频播放和暂停等功能。

效果预览

1

使用说明

  1. 在主界面,可以点击对应视频按钮进入视频播放页面
  2. 视频播放页面点击开启,应用拉起画中画,点击关闭关闭画中画
  3. 视频播放页面点击自动开启画中画,在返回桌面时会自动拉起画中画
  4. 视频播放页面会显示一些回调信息

具体实现

  • 整个示例用Navigation构建页面,主页面放置五个可点击视频框,点击之后进入视频播放页面。
  • 进入视频播放页面后,有三块区域,最上方的XComponent,中间的画中画控制按钮以及下方的回调信息显示框
  • 点击开启后,应用手动拉起画中画,视频在画中画播放,返回桌面视频依旧画中画播放;点击关闭后,画中画播放的视频返回XComponent播放,同时返回桌面不会拉起画中画。
  • 点击自动拉起画中画后,返回桌面时应用自动拉起画中画,视频画中画播放。
  • 在播放页面进行画中画播放时,XComponent框会提示当前视频正在以画中画播放
  • 回调信息显示框会显示当前状态错误原因以及按钮事件和状态,参考:[VideoPlay.ets]。
/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { PiPWindow } from '@kit.ArkUI';
import { JSON } from '@kit.ArkTS';
import { Constants } from '../constants/Constants';
import { AVPlayer } from './AVPlayer';
import Logger from '../utils/Logger';

const TAG = Constants.NAV_DESTINATION_NAME;

@Extend(Text)
function textType() {
  .padding({ left: $r('app.integer.other_padding') })
  .fontWeight(FontWeight.Bold)
  .fontSize($r('app.integer.text_size'))
  .alignSelf(ItemAlign.Start)
}

@Extend(Text)
function msgType() {
  .padding({ left: $r('app.integer.other_padding') })
  .fontSize($r('app.integer.text_size'))
  .fontColor($r('app.color.Message_color'))
  .alignSelf(ItemAlign.Start)
}

@Component
export struct PlayVideo {
  @Consume('pageInfos') pageInfos: NavPathStack;
  @State curState: string = '';
  @State curError: string = '';
  @State buttonAction: string = '';
  @State isAutoPull: boolean = false;
  @State isLightBackground: boolean = false;
  @State hintMsgVisibility: boolean = false;
  @State pipTypeString: string = '';
  mXComponentController = new XComponentController();
  surfaceId = '';
  navigationId: string = '';
  player?: AVPlayer;
  pipController?: PiPWindow.PiPController;
  eventHub = getContext().eventHub;
  private scrollerForScroll: Scroller = new Scroller()

  aboutToAppear(): void {
    this.eventHub.on('onStateChange', (fg: boolean) => {
      if (fg && this.curState === 'STARTED') {
        this.stopPip();
      }
    });
  }

  async startPip() {
    if (!this.pipController) {
      await this.createPipController();
    }
    if (!this.pipController) {
      Logger.info(`[${TAG}] pipController create error`);
      return;
    }
    await this.pipController.startPiP();
  }

  async stopPip() {
    if (!this.pipController) {
      Logger.info(`[${TAG}] pipController is not exist`);
      return;
    }
    await this.pipController.stopPiP();
  }

  async createPipController() {
    this.pipController = await PiPWindow.create({
      context: getContext(this),
      componentController: this.mXComponentController,
      navigationId: this.navigationId,
      templateType: PiPWindow.PiPTemplateType.VIDEO_PLAY
    });
    this.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);
    });
  }

  destroyPipController() {
    if (!this.pipController) {
      return;
    }
    this.pipController.off('stateChange');
    this.pipController.off('controlPanelActionEvent');
    this.pipController = undefined;
  }

  onStateChange(state: PiPWindow.PiPState, reason: string) {
    switch (state) {
      case PiPWindow.PiPState.ABOUT_TO_START:
        this.curState = 'ABOUT_TO_START';
        this.curError = Constants.ERROR_BY_DEFAULT;
        break;
      case PiPWindow.PiPState.STARTED:
        this.curState = 'STARTED';
        this.curError = Constants.ERROR_BY_DEFAULT;
        break;
      case PiPWindow.PiPState.ABOUT_TO_STOP:
        this.curState = 'ABOUT_TO_STOP';
        this.curError = Constants.ERROR_BY_DEFAULT;
        break;
      case PiPWindow.PiPState.STOPPED:
        this.player?.updatePlayStatus(true);
        this.player?.play();
        this.curState = 'STOPPED';
        this.curError = Constants.ERROR_BY_DEFAULT;
        break;
      case PiPWindow.PiPState.ABOUT_TO_RESTORE:
        this.curState = 'ABOUT_TO_RESTORE';
        this.curError = Constants.ERROR_BY_DEFAULT;
        break;
      case PiPWindow.PiPState.ERROR:
        this.curState = 'ERROR';
        this.curError = reason;
        break;
      default:
        break;
    }
    Logger.info(`[${TAG}] onStateChange: ${this.curState}, reason: ${reason}`);
  }

  onActionEvent(event: PiPWindow.PiPActionEventType, status: number | undefined) {
    switch (event) {
      case 'playbackStateChanged':
        if (status === 0) {
          this.player?.updatePlayStatus(false);
          this.player?.pause();
        } else {
          this.player?.updatePlayStatus(true);
          this.player?.play();
        }
        break;
      default:
        break;
    }
    this.buttonAction = event + `-status:${status}`;
    Logger.info(`[${TAG}] onActionEvent: ${this.buttonAction} status:${status}}`);
  }

  build() {
    Stack() {
      NavDestination() {
        Column({ space: Constants.SPACE }) {
          Stack() {
            Text($r('app.string.current_video_pip_play'))
              .fontColor($r('app.color.XComponent_text_color'))
              .margin({ bottom: $r('app.integer.x_component_marg_bottom') })
              .visibility(this.hintMsgVisibility ? Visibility.Visible : Visibility.Hidden)
            XComponent({ id: 'video', type: 'surface', controller: this.mXComponentController })
              .onLoad(() => {
                Logger.info(`[${TAG}] XComponent onLoad`);
                this.surfaceId = this.mXComponentController.getXComponentSurfaceId();
                this.player = new AVPlayer(this.surfaceId, Constants.AVPLAYER_TYPE);
                this.player.avPlayerFdSrc();
              })
              .onDestroy(() => {
                this.player?.stopAvPlayer();
                Logger.info(`[${TAG}] XComponent onDestroy`);
              })
              .size({ width: Constants.X_COMPONENT_WIDTH, height: $r('app.float.x_component_height') })
              .margin({ top: $r('app.integer.x_component_marg_top') })
              .backgroundColor(Color.Transparent)
              .align(Alignment.Bottom)
              .id('x_component')
          }
          .size({ width: Constants.X_COMPONENT_WIDTH, height: $r('app.float.x_component_height') })
          .alignContent(Alignment.Bottom)
          .backgroundColor($r('app.color.XComponent_backgroundColor'))

          Scroll(this.scrollerForScroll) {
            Column({ space: Constants.SPACE }) {
              this.ControlPip()
              this.AutoPip()
              this.CallbackMessage()
            }
            .width(Constants.NAV_DESTINATION_WIDTH)
          }
          .layoutWeight(Constants.SCROLL_LAY_OUT_WEIGHT)
          .scrollable(ScrollDirection.Vertical)
          .scrollBar(BarState.Off)
          .edgeEffect(EdgeEffect.Spring)
        }
        .width(Constants.NAV_DESTINATION_WIDTH)
        .height(Constants.NAV_DESTINATION_HEIGHT)
      }
      .hideTitleBar(true)
      .backgroundColor($r('app.color.Play_backgroundColor'))
      .onBackPressed(() => {
        // Eject the top-of-the-stack element of the routing stack.
        const popDestinationInfo = this.pageInfos.pop();
        Logger.info('pop' + 'return value' + JSON.stringify(popDestinationInfo));
        return true;
      })
    }
  }

  @Builder
  ControlPip() {
    Row({ space: Constants.SPACE }) {
      Button($r('app.string.start'))
        .width($r('app.integer.control_button_width'))
        .onClick(() => {
          this.startPip();
          this.hintMsgVisibility = true;
        })
      Button($r('app.string.stop'))
        .width($r('app.integer.control_button_width'))
        .onClick(() => {
          this.stopPip();
          this.hintMsgVisibility = false;
        })
    }
    .size({ width: Constants.CONTROL_WIDTH, height: $r('app.integer.control_height') })
    .justifyContent(FlexAlign.SpaceAround)
    .id('pip_control')
  }

  @Builder
  AutoPip() {
    Row() {
      Text($r('app.string.auto'))
        .width($r('app.integer.auto_text_width'))
        .fontSize($r('app.integer.text_size'))
        .fontWeight(FontWeight.Bold)
        .padding({ left: $r('app.integer.other_padding') })

      Toggle({ type: ToggleType.Switch, isOn: this.isAutoPull })
        .width($r('app.integer.auto_button_width'))
        .height($r('app.integer.auto_button_height'))
        .selectedColor($r('app.color.Toggle_selectedColor'))
        .padding({ right: $r('app.float.toggle_padding') })
        .onChange(async (isOn: boolean) => {
          this.isAutoPull = isOn;
          if (!this.pipController) {
            await this.createPipController();
          }
          this.pipController?.setAutoStartEnabled(this.isAutoPull);
          this.hintMsgVisibility = true;
        })
    }
    .width(Constants.AUTO_PIP_WIDTH)
    .height($r('app.integer.auto_pip_height'))
    .borderRadius($r('app.integer.auto_button_board_radius'))
    .justifyContent(FlexAlign.SpaceBetween)
    .backgroundColor($r('app.color.start_window_background'))
  }

  @Builder
  CallbackMessage() {
    Column({ space: Constants.SPACE }) {
      Text($r('app.string.callback_message'))
        .fontColor($r('app.color.Text_color'))
        .padding({ right: $r('app.integer.callback_text_padding') })
      Column() {
        Text($r('app.string.current_status'))
          .textType()
        Text(this.curState)
          .msgType()
      }
      .size({
        width: Constants.CONTROL_WIDTH,
        height: $r('app.integer.control_height')
      })
      .backgroundColor($r('app.color.Callback_message_backgroundColor'))
      .borderRadius($r('app.integer.auto_button_board_radius'))
      .justifyContent(FlexAlign.SpaceAround)
      .id('current_state')

      Column() {
        Text($r('app.string.current_error'))
          .textType()
        Text(this.curError)
          .msgType()
      }
      .size({
        width: Constants.CONTROL_WIDTH,
        height: $r('app.integer.control_height')
      })
      .backgroundColor($r('app.color.Callback_message_backgroundColor'))
      .borderRadius($r('app.integer.auto_button_board_radius'))
      .justifyContent(FlexAlign.SpaceAround)
      .id('current_error')

      Column() {
        Text($r('app.string.current_action'))
          .textType()
        Text(this.buttonAction)
          .msgType()
      }
      .size({
        width: Constants.CONTROL_WIDTH,
        height: $r('app.integer.control_height')
      })
      .backgroundColor($r('app.color.Callback_message_backgroundColor'))
      .borderRadius($r('app.integer.auto_button_board_radius'))
      .justifyContent(FlexAlign.SpaceAround)
      .id('current_action')
    }
  }
}

以上就是本篇文章所带来的鸿蒙开发中一小部分技术讲解;想要学习完整的鸿蒙全栈技术。可以在结尾找我可全部拿到!
下面是鸿蒙的完整学习路线,展示如下:
1

除此之外,根据这个学习鸿蒙全栈学习路线,也附带一整套完整的学习【文档+视频】,内容包含如下

内容包含了:(ArkTS、ArkUI、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、鸿蒙南向开发、鸿蒙项目实战)等技术知识点。帮助大家在学习鸿蒙路上快速成长!

鸿蒙【北向应用开发+南向系统层开发】文档

鸿蒙【基础+实战项目】视频

鸿蒙面经

在这里插入图片描述

为了避免大家在学习过程中产生更多的时间成本,对比我把以上内容全部放在了↓↓↓想要的可以自拿喔!谢谢大家观看!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1971751.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

三星One UI 7.0引入苹果的几大特色功能,iOS用户都羡慕哭了

在智能手机操作系统的创新之路上,苹果iOS系统的Dynamic Island和Live Activities功能无疑为用户带来了全新的交互体验。 现在,三星One UI 7.0系列的即将发布,似乎预示着安卓阵营也将迎头赶上,甚至可能在某些方面超越苹果。以下是…

AcWing-AcWing 837. 连通块中点的数量

在原来并查集的基础上增加一个Size数组,Size的初始化是必须先每个元素初始化为1 Size只对根节点有效,比如Size[find(1)]就是找1的祖先节点,然后访问祖先节点的个数。当我们联通a点和b点时,如果已经是联通状态了,那么无…

深度解读:等保测评标准与实践指南

在信息时代,数据安全与隐私保护成为企业和组织不可忽视的关键议题。等保测评,即信息安全等级保护测评,作为我国信息安全管理体系的重要组成部分,为各行业提供了标准化的安全评估与改进路径。本文旨在深度解读等保测评标准的核心要…

探索Python日期时间的宝藏:`dateutil`库的神秘面纱

文章目录 探索Python日期时间的宝藏:dateutil库的神秘面纱背景:为何选择dateutil?dateutil是什么?如何安装dateutil?简单函数介绍与使用parse函数:智能日期时间解析relativedelta:计算相对日期t…

一个超强的Python机器学习超参优化库

在机器学习模型的训练过程中,选择合适的超参数对模型性能的提升至关重要。超参数优化是指在给定的超参数空间内,找到一组能够使模型表现最佳的超参数组合。虽然有许多方法可以用来进行超参数优化,但在本文中,我们将重点介绍一个强大且易用的库——Optuna。 什么是Optuna?…

顺序表、单链表、顺序栈,链栈的基本运算

目录 顺序表的基本运算 单链表的基本运算 顺序栈的基本运算 链栈的基本运算 线性表的9个基本运算&#xff1a; 栈的6个基本运算&#xff1a; 顺序表的基本运算 //顺序表的基本运算************************************************************** #include<stdio…

通过yfinance获取股票历史数据

以比亚迪为例&#xff0c;要获取A股比亚迪的十年的历史数据并保存为CSV文件&#xff0c;我们可以使用Python中的第三方库如pandas和yfinance。yfinance库是一个用于下载雅虎财经数据的工具&#xff0c;它支持股票、期权等金融工具的数据获取。 1.安装yfinance和pandas 首先&a…

云服务器带宽什么意思?如何正确选择

云服务器带宽什么意思?云服务器带宽指的是云服务器在互联网上的数据传输能力。就像水流通过水管一样&#xff0c;数据通过所谓的“带宽”在网络中流动。这个带宽越大&#xff0c;每秒能够传输的数据就越多&#xff0c;对大量访问处理的能力也就越强。云服务器带宽是云服务器可…

【开源项目】基于RTP协议的H264码流发送器和接收器

RTP协议 1. 概述1.1 RTP协议1.2 RTP和UDP的关系 2. RTP打包H264码流2.1 RTP单一传输2.2 RTP分片传输2.3 RTP多片合入传输 3.工程3.1 头文件3.1.1 rtp.h3.1.2 utils.h 3.2 cpp文件3.2.1 rtp.cpp3.2.2 utils.cpp 4.测试5.小结 参考&#xff1a; 视音频数据处理入门&#xff1a;UD…

Arco Design 之Table表格

此篇文章为table表格示例&#xff0c;包含列、data数据、展开、选中、自定义等相关属性 基础表格 <a-table :columns"columns1" :data"tableData1" />const columns1 [{ title: "编号", dataIndex: "no"},{ title: "名称…

Linux线程2

线程相关函数 线程分离--pthread_detach&#xff08;后面会详细讲&#xff09; 函数原型&#xff1a;int pthread_datach(pthread_t thread); 调用该函数之后不需要 pthread_join 子线程会自动回收自己的PCB 杀死&#xff08;取消&#xff09;线程--pthread_cancel 取…

自动驾驶将驶向何方?大模型(World Models)自动驾驶综述

前言 自动驾驶系统的开发是一个技术与哲学的双重挑战&#xff0c;核心在于模拟人类的直觉推理和常识。尽管机器学习在模式识别上取得了进展&#xff0c;但在复杂情境下仍存在局限。人类决策基于感官感知&#xff0c;但能预见行动结果和预判变化&#xff0c;这是机器难以复制的…

欧科云链受邀参与EDCON 大会,听听OKLink为开发者带来哪些惊喜?

一年一度的 EDCON 大会于 7 月底在位于东京的联合国大学盛大举行。OKLink 与 Polygon 联手为来自全球各地的数千名开发者打造开放空间&#xff0c;带来多场精彩的主题分享&#xff0c;让开发者得以在上手体验的同时获取到关于最新开发工具的全面信息。 在论坛环节中&#xff0…

[Docker][Docker Container]详细讲解

目录 1.什么是容器&#xff1f;2.容器命令1.docker creatre2.docker run3.docker ps4.docker logs5.docker attach6.docker exec7.docker start8.docker stop9.docker restart10.docker kill11.docker top12.docker stats13.docker container inspect14.docker port15.docker c…

0730评价项目 实现数据库行转列查询

0730评价项目包-CSDN博客 数据库字段&#xff1a; 实现业务&#xff1a; 1&#xff09;查询对应部门&#xff0c;年份的员工季度评价信息&#xff1a; 对应sql语句&#xff1a; 使用 group by 和 GROUP_CONCAT 关键字进行行转列&#xff0c; case when 后接关联条件&#xf…

【Py/Java/C++三种语言详解】LeetCode 1334、LeetCode1334. 阈值距离内邻居最少的城市【全源最短路问题Floyd算法】

可上 欧弟OJ系统 练习华子OD、大厂真题 绿色聊天软件戳 od1441了解算法冲刺训练&#xff08;备注【CSDN】否则不通过&#xff09; 文章目录 相关推荐阅读**一、题目描述****二、题目解析****三、参考代码**PythonJavaC **四、时空复杂度**华为OD算法/大厂面试高频题算法练习冲刺…

webstorm配置项目Typescript编译环境

使用npm命令安装typeScript编译器 npm install typescript -g 安装好&#xff0c;在命令行可以查看编译器的版本 tsc --version 用Webstorm打开一个Typescript的项目。为TypeScript文件更改编译设置&#xff0c;File->Settings->toosl -> File Watchers->TypeScri…

【工具篇】华为VRP通用操作系统 —— 基础命令介绍

文章目录 视图切换命令命令报错误类型命令行快捷键 【工具篇】华为VRP通用操作系统 —— 基础知识 通过上一节的华为VRP通用操作系统介绍&#xff0c;掌握如何登入设备以及命令行架构。也通过eNSP虚拟器搭建拓扑成功登入华为VRP通用操作系统。 本文章介绍基础命令以及快捷键&am…

【数据结构】二叉树基本操作(孩子兄弟表示法 + Java详解 + 原码)

Hi~&#xff01;这里是奋斗的明志&#xff0c;很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~~ &#x1f331;&#x1f331;个人主页&#xff1a;奋斗的明志 &#x1f331;&#x1f331;所属专栏&#xff1a;数据结构 &#x1f4da;本系列文章为个人学…

OpenStack入门体验

一、云计算概述 1.1什么是云计算 云计算(cloud computing)是一种基于网络的超级计算模式,基于用户的不同需求&#xff0c;提供所需的资源&#xff0c;包括计算资源、存储资源、网络资源等。云计算服务运行在若干台高性能物理服务器之上&#xff0c;提供每秒 10万亿次的运算能力…