草庐IT

OpenHarmony仿视频播放器应用-爱电影(四)

徐金生 2023-03-29 原文

想了解更多关于开源的内容,请访问:

​51CTO 开源基础软件社区​

​https://ost.51cto.com​

效果

​在线视频​

接​​上一篇​​,视频播放页面属于小屏显示,为了让观演效果更好,可以选择全屏播放,全屏播放时界面由竖屏转为横屏显示,并且可以双向同步观影时间,无论是从视频播放页面进入全屏播放页面,还是由全屏播放页面返回到视频播放页面,只要处于播放在,就会同步播放时间,在页面切换后继续播放视频。当然,在全屏播放时页面处于横屏,返回到视频播放页面界面则切换回竖屏,我们来看下设计图:

从设计图上看,全屏播放页面的布局很简单,我们在上一节总已经将视频播放视图封装成了一个子组件—VideoView.ets,我们只要将其加载到全屏播放页面即可。

项目开发

开发环境

硬件平台:DAYU2000 RK3568
系统版本:OpenHarmony 3.2 beta5
SDK:9(3.2.10.6)
IDE:DevEco Studio 3.1 Beta1 Build Version: 3.1.0.200, built on February 13, 2023

程序代码

1、FullScreen.ets

/**
* 全屏播放
*/
import emitter from '@ohos.events.emitter';
import { CommonData } from '../model/CommonData'
import router from '@ohos.router';
import { VideoView } from '../view/VideoView';
import { VideoData } from '../model/VideoData'
import { VideoDataUtils } from '../utils/VideoDataUtils'
import { VideoSpeed } from '../model/VideoSpeed'
import { PLAYBACK_SPEED, PLAYBACK_STATE } from '../model/Playback'
const TAG: string = 'VideoFullScreen'
@Entry
@Component
struct VideoFullScreen {
@State mTag: string = TAG
@State mVideoData: VideoData = null
private name: string
@State uri: any = null
@State previewImage: any = null
private actors: string | Resource
private directs: string | Resource
private introduction: string
@State videoState: string = PLAYBACK_STATE.INIT
@Provide('play_time') curTime: number = 0
@State rateIndex: number = 1
@State rate: VideoSpeed = PLAYBACK_SPEED[1]
@Provide('show_operation') isShowOperation : boolean = true
aboutToAppear() {
// 横屏显示
emitter.emit({
eventId: CommonData.EVENT_WINDOW_LANDSCAPE_ID
})
this.initData()
}
initData() {
// 获取当前需要播放的电影资源信息
this.mVideoData = router.getParams()['video_data']
this.name = this.mVideoData.name
this.uri = this.mVideoData.uri
this.previewImage = this.mVideoData.image
this.actors = VideoDataUtils.getUser(this.mVideoData.actors)
this.directs = VideoDataUtils.getUser(this.mVideoData.directs)
this.introduction = this.mVideoData.introduction
this.curTime = router.getParams()['cur_time']
this.videoState = router.getParams()['video_state']
console.info(`${TAG} curTime:${this.curTime} videoState:${this.videoState}`)
}
onBackPress() {
console.info(`${TAG} onBackPress`)
this.sendPlayVideo()
}
onScreen(isFull: boolean) {
console.info(`${TAG} onScreen ${isFull}`)
if (!isFull) {
this.goBack()
}
}
sendPlayVideo() {
console.info(`${TAG} sendPlayVideo`)
emitter.emit({
eventId: CommonData.EVENT_PLAY_VIDEO
}, {
data: {
cur_time: this.curTime,
video_state: this.videoState
}
})
}
goBack() {
this.sendPlayVideo()
router.back()
}
aboutToDisappear() {
}
build() {
Stack({
alignContent: Alignment.TopStart
}) {
VideoView({
_TAG: this.mTag,
videoUri: $uri,
previewUri: $previewImage,
videoRate: $rate,
videoRateIndex: $rateIndex,
onScreen: this.onScreen.bind(this),
videoState: $videoState,
isFullScreen: true,
isEvent: false,
mWidth: '100%',
mHeight: '100%'
})
if (this.isShowOperation) {
Row({ space: 10 }) {
Image($r('app.media.icon_back'))
.width(24)
.height(24)
.objectFit(ImageFit.Cover)
.onClick(() => {
this.goBack()
})
Text(this.name)
.fontSize(20)
.fontColor(Color.White)
}
.padding(20)
}
}
.width('100%')
.height('100%')
}
}

界面代码非常简单,所有的功能在集成在VideoView组件中,这与视频播放页面相比,增加了电影播放倍数的选择,选择器使用​​Select​​下拉选择菜单实现,下面我们来详细的介绍下这个组件。

Select

提供了下拉选择菜单,让用户在多个选项之间选择。

Select(options: Array<SelectOption>)
SelectOption对象说明:

参数名

参数类型

必填

参数描述

value

ResourceStr

下拉选项内容。

icon

ResourceStr

下拉选项图片。

属性:

名称

参数类型

描述

selected

number

设置下拉菜单初始选项的索引,第一项的索引为0。

当不设置selected属性时,默认选择值为-1,菜单项不选中。

value

string

设置下拉按钮本身的文本内容。

font

Font

设置下拉按钮本身的文本样式。

fontColor

ResourceColor

设置下拉按钮本身的文本颜色。

selectedOptionBgColor

ResourceColor

设置下拉菜单选中项的背景色。

selectedOptionFont

Font

设置下拉菜单选中项的文本样式。

selectedOptionFontColor

ResourceColor

设置下拉菜单选中项的文本颜色。

optionBgColor

ResourceColor

设置下拉菜单项的背景色。

optionFont

Font

设置下拉菜单项的文本样式。

optionFontColor

ResourceColor

设置下拉菜单项的文本颜色。

事件:

名称

功能描述

onSelect(callback: (index: number, value?: string) => void)

下拉菜单选中某一项的回调。<br/>index:选中项的索引。<br/>value:选中项的值。

本案例中的Select组件是在VideoView.ets视频播放子组件中实现的,核心代码如下:

VideoView.ets

if (this.isFullScreen) {
Select(this.selectSpeedOption)
.selected(this.videoRateIndex)
.value(this.videoRate.val)
.font({ size: 10 })
.fontColor(Color.White)
.selectedOptionFont({ size: 10 })
.selectedOptionFontColor('#F54F02')
.optionFontColor('#5E5E5E')
.optionFont({ size: 10 })
.onSelect((index: number) => {
console.info('Select:' + index)
this.videoRate = PLAYBACK_SPEED[index]
this.videoRateIndex = index
console.info(`${TAG} videoRateIndex = ${this.videoRateIndex}`)
})
.border({
width: 0,
color: Color.White
})
}

2、横竖屏切换

如何实现横竖屏切换:

首先我们知道由于的界面需要集成到一个窗口上,这个窗口就是Window,在应用启动时会触发UIAbility的生命周期方法onWindowStageCreate(),此接口的回调中带有一个参数就是WindowStage窗口管理器,窗口管理器可以通过getMainWindow()接口获取到主窗口,返回当前窗口的实例Window,得到窗口实例后就可以通过setPreferredOrientation()设置窗口的显示方向。

setPreferredOrientation:
setPreferredOrientation(orientation: Orientation, callback: AsyncCallback<void" style="font: revert; -webkit-font-smoothing: antialiased; margin: 0px; padding: 0px; border: 0px; vertical-align: baseline; color: rgb(166, 127, 89); cursor: help;">>): void

设置窗口的显示方向属性,使用callback异步回调。

参数:

参数名

类型

必填

说明

Orientation

Orientation

窗口显示方向的属性。

callback

AsyncCallback<void>

回调函数。

Orientation:

窗口显示方向类型枚举。

名称

说明

UNSPECIFIED

0

表示未定义方向模式,由系统判定。

PORTRAIT

1

表示竖屏显示模式。

LANDSCAPE

2

表示横屏显示模式。

PORTRAIT_INVERTED

3

表示反向竖屏显示模式。

LANDSCAPE_INVERTED

4

表示反向横屏显示模式。

AUTO_ROTATION

5

表示传感器自动旋转模式。

AUTO_ROTATION_PORTRAIT

6

表示传感器自动竖向旋转模式。

AUTO_ROTATION_LANDSCAPE

7

表示传感器自动横向旋转模式。

AUTO_ROTATION_RESTRICTED

8

表示受开关控制的自动旋转模式。

AUTO_ROTATION_PORTRAIT_RESTRICTED

9

表示受开关控制的自动竖向旋转模式。

AUTO_ROTATION_LANDSCAPE_RESTRICTED

10

表述受开关控制的自动横向旋转模式。

LOCKED

11

表示锁定模式。

具体如何实现呢?
我们知道由于启动时会加重UIAbility,在项目中EntryAbility继承UIAbility,所以可以在EntryAbility.ts中获取Window实例设置其窗口显示方向来实现横竖屏切换,代码如下:

import UIAbility from '@ohos.app.ability.UIAbility';
import hilog from '@ohos.hilog';
import window from '@ohos.window';
import emitter from '@ohos.events.emitter';
import { CommonData } from '../model/CommonData'
export default class EntryAbility extends UIAbility {
private mWindow : window.Window
onCreate(want, launchParam) {

}
onDestroy() {

// 设置竖屏
this.mWindow.setPreferredOrientation(window.Orientation.PORTRAIT)
this.unregisterEmitter()
}
onWindowStageCreate(windowStage: window.WindowStage) {
// Main window is created, set main page for this ability
this.mWindow = windowStage.getMainWindowSync()
this.registerEmitter()
windowStage.loadContent('pages/Splash', (err, data) => {
if (err.code) {
return;
}
});
}

registerEmitter() {
emitter.on({
eventId : CommonData.EVENT_WINDOW_PORTRAIT_ID
}, () => {
if (!this.mWindow) {
return
}
this.mWindow.setPreferredOrientation(window.Orientation.PORTRAIT)
})
emitter.on({
eventId : CommonData.EVENT_WINDOW_LANDSCAPE_ID
}, () => {
if (!this.mWindow) {
return
}
this.mWindow.setPreferredOrientation(window.Orientation.LANDSCAPE)
})
}
unregisterEmitter() {
emitter.off(CommonData.EVENT_WINDOW_PORTRAIT_ID)
emitter.off(CommonData.EVENT_WINDOW_LANDSCAPE_ID)
}
}

由于视频播放页面和全屏播放页面与EntryAbility无直接联系,如果在操作页面时修改窗口方向呢?我相信你也注意到了上面的代码中使用到了@ohos.events.emitter,​emitter提供了在同一进程不同线程之间或者同一进程同一线程内,发送和处理事件的能力,可以通过订阅事件、取消订阅、发送事件等接口实现消息线程通信。所以我们在EntryAbility的onWindowStageCreate()接口回调时订阅了横竖屏切换事件,当然在应用退出时,也就是在onDestroy()接口被回调时,应该注取消订阅,防止内存泄漏,消息错乱。

发送横竖屏切换事件:
  • 播放页面切换到全屏播放时界面切换成横屏,需要在FullScreen.ets界面被启动回调aboutToAppear()接口时发送横屏事件,通知Window修改方向。FullScreen.ets中的核对代码:
aboutToAppear() {
// 横屏显示
emitter.emit({
eventId: CommonData.EVENT_WINDOW_LANDSCAPE_ID
})
}
  • 全屏播放返回到视频播放页时需要将横屏切换到竖屏显示,所以当Playback.ets页面的onPageShow()接口被触发时,就发送竖屏事件,通知Window修改方向。Playback.ets中的核心代码:
onPageShow() {
// 竖屏显示
emitter.emit({
eventId: CommonData.EVENT_WINDOW_PORTRAIT_ID
})
}

这样就完成了视频播放页面为竖屏,全屏播放为横屏的功能。

3、播放时间同步

播放时间同步主要在视频播放页面与全屏播放页面相互切换时使用,在两个页面切换时,除了时间同步外,播放状态也需要同步。时间同步是指:视频播放页面在播放视频时,假设播放到5s这个时间帧节点时,切换到全屏播放页面,全屏播放进入播放状态,且从5s这个时间帧节点开始播放。

如上所述,两个页面之间必须同步播放时间戳,页面切换通过路由器@ohos. router 实现,在router.pushUrl()函数中可以添加参数,我们将时间戳通过自定义参数传递到目标界面,页面返回到上一级页面时,一般使用router.back(),此时通过发送事件同步消息实现视频播放时间同步。具体实现请参看FullScreen.ets、Playback.ets、VideoView.ets三个类。

这个就是全屏播放页面的实现,到目前已经将视频播放器的所有页面实现讲述完毕。

想了解更多关于开源的内容,请访问:

​51CTO 开源基础软件社区​

​https://ost.51cto.com​

有关OpenHarmony仿视频播放器应用-爱电影(四)的更多相关文章

  1. ruby - 将差异补丁应用于字符串/文件 - 2

    对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl

  2. ruby-on-rails - Rails 应用程序之间的通信 - 2

    我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此

  3. ruby - 无法运行 Rails 2.x 应用程序 - 2

    我尝试运行2.x应用程序。我使用rvm并为此应用程序设置其他版本的ruby​​:$rvmuseree-1.8.7-head我尝试运行服务器,然后出现很多错误:$script/serverNOTE:Gem.source_indexisdeprecated,useSpecification.Itwillberemovedonorafter2011-11-01.Gem.source_indexcalledfrom/Users/serg/rails_projects_terminal/work_proj/spohelp/config/../vendor/rails/railties/lib/r

  4. ruby-on-rails - Rails 应用程序中的 Rails : How are you using application_controller. rb 是新手吗? - 2

    刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr

  5. ruby-on-rails - 如何在我的 Rails 应用程序 View 中打印 ruby​​ 变量的内容? - 2

    我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby​​中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R

  6. ruby-on-rails - 如何在 Gem 中获取 Rails 应用程序的根目录 - 2

    是否可以在应用程序中包含的gem代码中知道应用程序的Rails文件系统根目录?这是gem来源的示例:moduleMyGemdefself.included(base)putsRails.root#returnnilendendActionController::Base.send:include,MyGem谢谢,抱歉我的英语不好 最佳答案 我发现解决类似问题的解决方案是使用railtie初始化程序包含我的模块。所以,在你的/lib/mygem/railtie.rbmoduleMyGemclassRailtie使用此代码,您的模块将在

  7. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  8. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  9. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  10. ruby-on-rails - 从应用程序中自定义文件夹内的命名空间自动加载 - 2

    我们目前正在为ROR3.2开发自定义cms引擎。在这个过程中,我们希望成为我们的rails应用程序中的一等公民的几个类类型起源,这意味着它们应该驻留在应用程序的app文件夹下,它是插件。目前我们有以下类型:数据源数据类型查看我在app文件夹下创建了多个目录来保存这些:应用/数据源应用/数据类型应用/View更多类型将随之而来,我有点担心应用程序文件夹被这么多目录污染。因此,我想将它们移动到一个子目录/模块中,该子目录/模块包含cms定义的所有类型。所有类都应位于MyCms命名空间内,目录布局应如下所示:应用程序/my_cms/data_source应用程序/my_cms/data_ty

随机推荐