#创作者激励#OpenHarmony仿视频播放器应用-爱电影(四) 原创

NL_AIDC_XJS
发布于 2023-3-23 16:52
浏览
0收藏

【本文正在参加2023年第一期优质创作者激励计划】
作者:徐金生

仿视频播放器应用-爱电影合集

效果

在线视频

接上一篇,视频播放页面属于小屏显示,为了让观演效果更好,可以选择全屏播放,全屏播放时界面由竖屏转为横屏显示,并且可以双向同步观影时间,无论是从视频播放页面进入全屏播放页面,还是由全屏播放页面返回到视频播放页面,只要处于播放在,就会同步播放时间,在页面切换后继续播放视频。当然,在全屏播放时页面处于横屏,返回到视频播放页面界面则切换回竖屏,我们来看下设计图:
#创作者激励#OpenHarmony仿视频播放器应用-爱电影(四)-鸿蒙开发者社区
#创作者激励#OpenHarmony仿视频播放器应用-爱电影(四)-鸿蒙开发者社区
从设计图上看,全屏播放页面的布局很简单,我们在上一节总已经将视频播放视图封装成了一个子组件—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。<br>当不设置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、横竖屏切换
2.1、如何实现横竖屏切换

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

setPreferredOrientation
setPreferredOrientation(orientation: Orientation, callback: AsyncCallback&lt;void&gt;): 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()接口被回调时,应该注取消订阅,防止内存泄漏,消息错乱。

2.2、发送横竖屏切换事件
  • 播放页面切换到全屏播放时界面切换成横屏,需要在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三个类。

这个就是全屏播放页面的实现,到目前已经将视频播放器的所有页面实现讲述完毕,如果你有遇到什么问题欢迎留言讨论。

感谢

如果您能看到最后,还希望您能动动手指点个赞,一个人能走多远关键在于与谁同行,我用跨越山海的一路相伴,希望得到您的点赞。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2023-3-23 16:57:33修改
3
收藏
回复
举报
2条回复
按时间正序
/
按时间倒序
红叶亦知秋
红叶亦知秋

也都是非常实用的功能

回复
2023-3-24 10:24:30
zc新手一枚
zc新手一枚

牛的哇

回复
2023-9-1 14:47:28
回复
    相关推荐