/**
 * Video组件的样式参考了[uni-app](https://github.com/dcloudio/uni-app/tree/master/packages/uni-h5)的实现
 */ import React , { Component } from 'react' ; import { formatTime } from './utils' ; import { VideoProps } from '@tarojs/components/types/Video' ; import { AVPlaybackStatus , Video , VideoFullscreenUpdateEvent , VideoReadyForDisplayEvent , } from 'expo-av' ; import Styles from './style' ; import View from '../View' ; import Text from '../Text' ; import Image from '../Image' ; import { onFullscreenChangeEventDetail } from './PropsType' ; typedef Danmu property { string } text property { string } color property { number } time typedef VideoProps property { string } src property { boolean } autoPauseIfNavigate property { boolean } autoPauseIfOpenNative property { boolean } autoplay property { boolean } controls property { boolean } danmuBtn property { Array . < Danmu > } danmuList property { boolean } enableDanmu property { boolean } enablePlayGesture property { boolean } enableProgressGesture property { number } initialTime property { boolean } loop property { boolean } muted property { string } objectFit property { string } playBtnPosition property { boolean } showCenterPlayBtn property { boolean } showFullscreenBtn property { boolean } showMuteBtn property { boolean } showPlayBtn property { boolean } showProgress property { boolean } vslideGesture property { boolean } vslideGestureInFullscreen property { number } direction property { number } duration property { string } poster property { string } title property { Function } onPlay property { Function } onPause property { Function } onEnded property { Function } onTimeUpdate property { Function } onFullscreenChange property { Function } onWaiting property { Function } onError property { Function } onProgress const ObjectFit = { contain : Video . RESIZE_MODE_CONTAIN , fill : Video . RESIZE_MODE_STRETCH , cover : Video . RESIZE_MODE_COVER , } ; declare const global ; global . _taroVideoMap = { } ; interface Props extends VideoProps { onLoad : ( ) => void ; } class _Video extends Component < Props , any > { type { VideoProps } static defaultProps : Props = { id : '' , src : '' , autoPauseIfNavigate : true , autoPauseIfOpenNative : true , autoplay : false , controls : true , danmuBtn : false , danmuList : [ ] , enableDanmu : false , enablePlayGesture : false , enableProgressGesture : true , initialTime : 0 , loop : false , muted : false , objectFit : 'contain' , playBtnPosition : 'bottom' , showCenterPlayBtn : true , showFullscreenBtn : true , showMuteBtn : false , showPlayBtn : true , showProgress : true , vslideGesture : false , vslideGestureInFullscreen : true , onLoad : ( ) => { } , } ; type { HTMLVideoElement } videoRef : Video ; type { number } currentTime = 0 ; progressDimentions = { left : 0 , right : 0 , width : 0 , } ; getVideoRef : ( ref : any ) => void ; isDraggingProgress : any ; duration : any ; toastVolumeRef : any ; toastVolumeBarRef : any ; toastProgressTitleRef : any ; toastProgressRef : any ; getControlsRef : ( ref : any ) => void ; getDanmuRef : ( ref : any ) => void ; getToastProgressRef : ( ref : any ) => void ; getToastProgressTitleRef : ( ref : any ) => void ; getToastVolumeRef : ( ref : any ) => void ; getToastVolumeBarRef : ( ref : any ) => void ; unbindTouchEvents : ( ) => void ; constructor ( { props , context } ) { super ( props , context ) ; const stateObj = this . props ; this . videoRef = ( React . createRef ( ) as unknown ) as Video ; this . state = Object . assign ( { duration : null , isPlaying : false , isFirst : true , enableDanmu : false , isFullScreen : false , isMute : false , } , stateObj ) ; } onEnded = ( e ) : void => { this . setState ( { isEnded : true , } ) ; if ( ! this . props . loop ) this . pause ( ) ; this . props . onEnded && this . props . onEnded ( e ) ; } ; onPlay = ( e ) : void => { this . props . onPlay && this . props . onPlay ( e ) ; if ( ! this . state . isPlaying ) { this . play ( ) ; } } ; onPause = ( e ) : void => { this . props . onPause && this . props . onPause ( e ) ; if ( this . state . isPlaying ) { this . setState ( { isPlaying : false , } ) ; } } ; onError = ( e ) : void => { if ( this . props . onError ) { const error = Object . defineProperty ( { } , 'detail' , { enumerable : true , value : { errMsg : e } , } ) ; this . props . onError ( error ) ; } } ; onLoad = ( status ) : void => { const { durationMillis = 0 } = status ; this . setState ( { duration : durationMillis , } ) ; this . props . onLoad && this . props . onLoad ( ) ; } ; clickPlayBtn = ( ) : void => { const { isEnded } = this . state ; isEnded && this . seek ( 0 ) ; this . videoRef && this . videoRef . playAsync ( ) ; this . setState ( { shouldPlay : true , isFirst : false , } ) ; } ; play = ( ) : void => { const { isEnded } = this . state ; isEnded && this . seek ( 0 ) ; this . videoRef . playAsync ( ) ; this . setState ( { shouldPlay : true , isPlaying : true , isFirst : false , } ) ; } ; pause = ( ) : void => { this . setState ( { isPlaying : false , shouldPlay : false , } ) ; } ; stop = ( ) : void => { this . pause ( ) ; this . seek ( 0 ) ; this . setState ( { isPlaying : false , } ) ; } ; seek = ( position ) : void => { this . videoRef . setStatusAsync ( { positionMillis : position , } ) ; } ; showStatusBar = ( ) : void => { console . error ( '暂不支持 videoContext.showStatusBar' ) ; } ; hideStatusBar = ( ) : void => { console . error ( '暂不支持 videoContext.hideStatusBar' ) ; } ; requestFullScreen = ( ) : void => { this . videoRef . presentFullscreenPlayer ( ) ; } ; exitFullScreen = ( ) : void => { this . videoRef . dismissFullscreenPlayer ( ) ; } ; componentDidMount ( ) { const getRef = ( refName ) => { const { id } = this . props ; return ( ref ) => { if ( ! ref ) return ; this [ refName ] = ref ; id && ( global . _taroVideoMap [ id ] = ref ) ; } ; } ; this . getVideoRef = getRef ( 'videoRef' ) ; } static getDerivedStateFromProps ( nProps ) { return nProps ; } onLoadedMetaData = ( event ) : void => { const { naturalSize , status } = event ; status . duration = status . durationMillis ; this . props . onLoadedMetaData && this . props . onLoadedMetaData ( { detail : { ... naturalSize , ... status } } ) ; } ; onFullscreenChange = ( event ) : void => { const { fullscreenUpdate , status } = event ; const fullScreen = fullscreenUpdate === Video . FULLSCREEN_UPDATE_PLAYER_WILL_PRESENT || fullscreenUpdate === Video . FULLSCREEN_UPDATE_PLAYER_DID_PRESENT ; const detail = { fullScreen : fullscreenUpdate === Video . FULLSCREEN_UPDATE_PLAYER_WILL_PRESENT || fullscreenUpdate === Video . FULLSCREEN_UPDATE_PLAYER_DID_PRESENT , fullscreenUpdate , direction : 1 , ... status , } ; if ( this . state . isFullScreen !== fullScreen ) { this . setState ( { isFullScreen : fullScreen , } , ( ) => { this . props . onFullscreenChange && this . props . onFullscreenChange ( { detail } ) ; } ) ; } } ; onPlaybackStatusUpdate = ( event ) : void => { this . props . onTimeUpdate && this . props . onTimeUpdate ( event ) ; const { didJustFinish , isPlaying } = event ; if ( didJustFinish ) { this . onEnded ( event ) ; } if ( isPlaying !== this . state . isPlaying ) { this . setState ( { isPlaying , isFirst : isPlaying ? false : this . state . isFirst } , ( ) => { isPlaying && this . onPlay ( event ) ; ! isPlaying && ! this . state . isFirst && this . onPause ( event ) ; } ) ; } } ; render ( ) { const { src , autoplay , style , initialTime , loop , muted , objectFit = 'contain' , poster , controls , showCenterPlayBtn , } = this . props ; const { isFullScreen , shouldPlay } = this . state ; const duration = formatTime ( this . props . duration || this . state . duration || null ) ; const videoProps = { source : { uri : src } , shouldPlay : shouldPlay || autoplay , posterSource : controls ? { uri : poster } : undefined , usePoster : ! ! controls , isLooping : loop , isMuted : muted , positionMillis : initialTime , style : Object . assign ( { width : '100%' , height : '100%' } , style as Record < string , unknown > ) , ref : this . getVideoRef , resizeMode : ObjectFit [ objectFit ] , useNativeControls : controls , onError : this . onError , onLoad : this . onLoad , onReadyForDisplay : this . onLoadedMetaData , onFullscreenUpdate : this . onFullscreenChange , onPlaybackStatusUpdate : this . onPlaybackStatusUpdate , } ; const showPlayBtn = this . state . isFirst ? true : showCenterPlayBtn ; const videoNode = ( < View style = { [ Styles [ 'taro-video-container' ] , isFullScreen && Styles [ 'taro-video-type-fullscreen' ] , ] } > < Video { ... videoProps } / > { showPlayBtn && ! this . state . isPlaying && ( < View style = { Styles [ 'taro-video-cover' ] } > < Image src = { require ( '../../assets/video/play.png' ) } style = { Styles [ 'taro-video-cover-play-button' ] } onClick = { this . clickPlayBtn } / > < Text style = { Styles [ 'taro-video-cover-duration' ] } > { duration } < /Text> < /View>)} { this . props . children } < /View>); return ( < View style = { [ Styles [ 'taro-video' ] , this . props . style as Record < string , unknown > ] } > { videoNode } < /View>); } } export default _Video ;