無論是從視頻播放頁面進入全屏播放頁面,還是由全屏播放頁面返回到視頻播放頁面,只要處于播放在,就會同步播放時間,在頁面切換后繼續(xù)播放視頻。當然,在全屏播放時頁面處于橫屏,返回到視頻播放頁面界面則切換回豎屏。
 
??想了解更多關于開源的內容,請訪問:??
??51CTO 開源基礎軟件社區(qū)??
??https://ost.51cto.com??
效果
??在線視頻??
接??上一篇??,視頻播放頁面屬于小屏顯示,為了讓觀演效果更好,可以選擇全屏播放,全屏播放時界面由豎屏轉為橫屏顯示,并且可以雙向同步觀影時間,無論是從視頻播放頁面進入全屏播放頁面,還是由全屏播放頁面返回到視頻播放頁面,只要處于播放在,就會同步播放時間,在頁面切換后繼續(xù)播放視頻。當然,在全屏播放時頁面處于橫屏,返回到視頻播放頁面界面則切換回豎屏,我們來看下設計圖:


從設計圖上看,全屏播放頁面的布局很簡單,我們在上一節(jié)總已經將視頻播放視圖封裝成了一個子組件—VideoView.ets,我們只要將其加載到全屏播放頁面即可。
項目開發(fā)
開發(fā)環(huán)境
硬件平臺:DAYU2000 RK3568
系統(tǒng)版本: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,在應用啟動時會觸發(fā)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  | 表示未定義方向模式,由系統(tǒng)判定。  | 
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無直接聯(lián)系,如果在操作頁面時修改窗口方向呢?我相信你也注意到了上面的代碼中使用到了@ohos.events.emitter,?emitter提供了在同一進程不同線程之間或者同一進程同一線程內,發(fā)送和處理事件的能力,可以通過訂閱事件、取消訂閱、發(fā)送事件等接口實現消息線程通信。所以我們在EntryAbility的onWindowStageCreate()接口回調時訂閱了橫豎屏切換事件,當然在應用退出時,也就是在onDestroy()接口被回調時,應該注取消訂閱,防止內存泄漏,消息錯亂。
發(fā)送橫豎屏切換事件:- 播放頁面切換到全屏播放時界面切換成橫屏,需要在FullScreen.ets界面被啟動回調aboutToAppear()接口時發(fā)送橫屏事件,通知Window修改方向。FullScreen.ets中的核對代碼:
 
aboutToAppear() {
    // 橫屏顯示
    emitter.emit({
      eventId: CommonData.EVENT_WINDOW_LANDSCAPE_ID
    })
  }- 全屏播放返回到視頻播放頁時需要將橫屏切換到豎屏顯示,所以當Playback.ets頁面的onPageShow()接口被觸發(fā)時,就發(fā)送豎屏事件,通知Window修改方向。Playback.ets中的核心代碼:
 
onPageShow() {
    // 豎屏顯示
    emitter.emit({
      eventId: CommonData.EVENT_WINDOW_PORTRAIT_ID
    })
  }這樣就完成了視頻播放頁面為豎屏,全屏播放為橫屏的功能。
3、播放時間同步
播放時間同步主要在視頻播放頁面與全屏播放頁面相互切換時使用,在兩個頁面切換時,除了時間同步外,播放狀態(tài)也需要同步。時間同步是指:視頻播放頁面在播放視頻時,假設播放到5s這個時間幀節(jié)點時,切換到全屏播放頁面,全屏播放進入播放狀態(tài),且從5s這個時間幀節(jié)點開始播放。
如上所述,兩個頁面之間必須同步播放時間戳,頁面切換通過路由器@ohos. router 實現,在router.pushUrl()函數中可以添加參數,我們將時間戳通過自定義參數傳遞到目標界面,頁面返回到上一級頁面時,一般使用router.back(),此時通過發(fā)送事件同步消息實現視頻播放時間同步。具體實現請參看FullScreen.ets、Playback.ets、VideoView.ets三個類。
這個就是全屏播放頁面的實現,到目前已經將視頻播放器的所有頁面實現講述完畢。
??想了解更多關于開源的內容,請訪問:??
??51CTO 開源基礎軟件社區(qū)??
??https://ost.51cto.com??