画中画效果实现
介绍
本示例通过@kit.ArkUI、@kit.MediaKit等接口,实现了视频播放、手动和自动拉起画中画、画中画窗口控制视频播放和暂停等功能。
效果预览
使用说明
- 在主界面,可以点击对应视频按钮进入视频播放页面;
- 视频播放页面点击开启,应用拉起画中画,点击关闭,关闭画中画;
- 视频播放页面点击自动开启画中画,在返回桌面时会自动拉起画中画;
- 视频播放页面会显示一些回调信息。
具体实现
- 整个示例用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;})}}@BuilderControlPip() {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')}@BuilderAutoPip() {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'))}@BuilderCallbackMessage() {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')}}
}
以上就是本篇文章所带来的鸿蒙开发>鸿蒙开发中一小部分技术讲解;想要学习完整的鸿蒙全栈技术。可以在结尾找我可全部拿到!
下面是鸿蒙的完整学习路线,展示如下:
除此之外,根据这个学习鸿蒙全栈学习路线,也附带一整套完整的学习【文档+视频】,内容包含如下:
内容包含了:(ArkTS、ArkUI、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、鸿蒙南向开发、鸿蒙项目实战)等技术知识点。帮助大家在学习鸿蒙路上快速成长!
鸿蒙【北向应用开发+南向系统层开发】文档
鸿蒙【基础+实战项目】视频
鸿蒙面经
为了避免大家在学习过程中产生更多的时间成本,对比我把以上内容全部放在了↓↓↓想要的可以自拿喔!谢谢大家观看!