diff --git a/projects/packages/videopress/changelog/update-rnmobile-android-player b/projects/packages/videopress/changelog/update-rnmobile-android-player new file mode 100644 index 0000000000000..c59b9886badab --- /dev/null +++ b/projects/packages/videopress/changelog/update-rnmobile-android-player @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +Add manual controls to the player on Android diff --git a/projects/packages/videopress/src/client/block-editor/blocks/video/components/icons/index.native.js b/projects/packages/videopress/src/client/block-editor/blocks/video/components/icons/index.native.js index 5ca9ccc1238ac..6affc4efb4327 100644 --- a/projects/packages/videopress/src/client/block-editor/blocks/video/components/icons/index.native.js +++ b/projects/packages/videopress/src/client/block-editor/blocks/video/components/icons/index.native.js @@ -25,3 +25,31 @@ export const retryIcon = ( ); + +export const PauseIcon = ( + + + +); + +export const PlayIcon = ( + + + +); + +export const ReplayIcon = ( + + + +); diff --git a/projects/packages/videopress/src/client/block-editor/blocks/video/components/player/controls/index.native.js b/projects/packages/videopress/src/client/block-editor/blocks/video/components/player/controls/index.native.js new file mode 100644 index 0000000000000..b033258ea232d --- /dev/null +++ b/projects/packages/videopress/src/client/block-editor/blocks/video/components/player/controls/index.native.js @@ -0,0 +1,91 @@ +/** + * WordPress dependencies + */ +import { Icon } from '@wordpress/components'; +import { useState, useEffect, useCallback, useRef } from '@wordpress/element'; +/** + * External dependencies + */ +import { View, Pressable } from 'react-native'; +/** + * Internal dependencies + */ + +import { PauseIcon, PlayIcon, ReplayIcon } from '../../icons'; +import style from './style.scss'; + +const PlayerControls = ( { isSelected, playEnded, onToggle } ) => { + const [ showControlIcon, setShowControlIcon ] = useState( true ); + const [ isPlaying, setIsPlaying ] = useState( false ); + const [ isFinishedPlaying, setIsFinishedPlaying ] = useState( playEnded ); + + // Update the state when the video ends. + useEffect( () => { + if ( playEnded ) { + setShowControlIcon( true ); + } + setIsFinishedPlaying( playEnded ); + }, [ playEnded ] ); + + // Clear out the state when deselected. + useEffect( () => { + if ( ! isSelected ) { + setIsPlaying( false ); + onToggle( 'pause' ); + setIsFinishedPlaying( false ); + setShowControlIcon( true ); + } + }, [ isSelected ] ); + + // Hide the play/pause button after a short delay. + const hidePauseTimer = useRef(); + useEffect( () => { + if ( isPlaying ) { + hidePauseTimer.current = setTimeout( () => { + ! isFinishedPlaying && setShowControlIcon( false ); + }, 800 ); + } + return () => { + clearTimeout( hidePauseTimer.current ); + }; + }, [ isPlaying, isFinishedPlaying ] ); + + const togglePlayState = useCallback( () => { + const nextEvent = ! isPlaying || isFinishedPlaying ? 'play' : 'pause'; + + setIsFinishedPlaying( false ); + setShowControlIcon( true ); + setIsPlaying( nextEvent === 'play' ); + + onToggle( nextEvent ); + }, [ isPlaying, isFinishedPlaying ] ); + + let icon = PlayIcon; + + if ( isPlaying ) { + icon = PauseIcon; + } + + if ( isFinishedPlaying ) { + icon = ReplayIcon; + } + + const iconStyle = style[ 'videopress-player__overlay-controls-button-icon' ]; + const renderButton = () => ( + + { showControlIcon && } + + ); + + if ( ! isSelected ) { + return renderButton(); + } + + return ( + + { renderButton() } + + ); +}; + +export default PlayerControls; diff --git a/projects/packages/videopress/src/client/block-editor/blocks/video/components/player/controls/style.native.scss b/projects/packages/videopress/src/client/block-editor/blocks/video/components/player/controls/style.native.scss new file mode 100644 index 0000000000000..902712dcfa36c --- /dev/null +++ b/projects/packages/videopress/src/client/block-editor/blocks/video/components/player/controls/style.native.scss @@ -0,0 +1,22 @@ +.videopress-player__overlay-controls { + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 100%; + z-index: 1; +} + +.videopress-player__overlay-controls-button { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + width: 100%; +} + +.videopress-player__overlay-controls-button-icon { + width: 64px; + height: 64px; + size: 64; +} diff --git a/projects/packages/videopress/src/client/block-editor/blocks/video/components/player/index.native.js b/projects/packages/videopress/src/client/block-editor/blocks/video/components/player/index.native.js index 3fe7f2fe4c53c..112c3afd6012d 100644 --- a/projects/packages/videopress/src/client/block-editor/blocks/video/components/player/index.native.js +++ b/projects/packages/videopress/src/client/block-editor/blocks/video/components/player/index.native.js @@ -9,7 +9,7 @@ import { __ } from '@wordpress/i18n'; /** * External dependencies */ -import { View, Text } from 'react-native'; +import { View, Text, Platform } from 'react-native'; /** * Internal dependencies */ @@ -18,10 +18,12 @@ import { getVideoPressUrl } from '../../../../../lib/url'; import { usePreview } from '../../../../hooks/use-preview'; import addTokenIntoIframeSource from '../../../../utils/add-token-iframe-source'; import { VideoPressIcon } from '../icons'; +import PlayerControls from './controls'; import style from './style.scss'; const VIDEO_PREVIEW_ATTEMPTS_LIMIT = 10; const DEFAULT_PLAYER_ASPECT_RATIO = 16 / 9; // This is the observed default aspect ratio from VideoPress embeds. +const IS_ANDROID = Platform.OS === 'android'; /** * VideoPlayer react component @@ -49,11 +51,21 @@ export default function Player( { isSelected, attributes } ) { const loadingViewStyle = style[ 'videopress-player__loading' ]; const [ isPlayerLoaded, setIsPlayerLoaded ] = useState( false ); - const [ isPlayerReady, setIsPlayerReady ] = useState( false ); + const [ isPlayerReady, setIsPlayerReady ] = useState( IS_ANDROID ); const [ token, setToken ] = useState(); const [ previewCheckAttempts, setPreviewCheckAttempts ] = useState( 0 ); const previewCheckTimer = useRef(); + // Used for Android controls only + const [ playEnded, setPlayEnded ] = useState( false ); + const playerRef = useRef(); + const onToggleEvent = useCallback( event => { + setPlayEnded( false ); + playerRef.current?.injectJavaScript( ` + document?.querySelector('iframe')?.contentWindow.postMessage({event: 'videopress_action_${ event }'}, '*'); + ` ); + }, [] ); + // Fetch token for a VideoPress GUID useEffect( () => { if ( guid ) { @@ -67,13 +79,13 @@ export default function Player( { isSelected, attributes } ) { useEffect( () => { if ( guid ) { setIsPlayerLoaded( false ); - setIsPlayerReady( false ); + setIsPlayerReady( IS_ANDROID ); } }, [ guid ] ); const videoPressUrl = getVideoPressUrl( guid, { autoplay: false, // Note: Autoplay is disabled to prevent the video from playing fullscreen when loading the editor. - controls, + controls: ! IS_ANDROID && controls, loop, muted, playsinline, @@ -123,6 +135,15 @@ export default function Player( { isSelected, attributes } ) { case 'videopress_loading_state': setIsPlayerLoaded( message?.state === 'loaded' ); break; + // Events use for the Android controls + case 'videopress_ended': + setPlayEnded( true ); + break; + case 'videopress_playing': + if ( playEnded ) { + setPlayEnded( false ); + } + break; } }, [] ); @@ -137,6 +158,25 @@ export default function Player( { isSelected, attributes } ) { ); + const renderOverlay = () => { + // Show custom controls on Android only + if ( IS_ANDROID && isPlayerLoaded ) { + return ( + + + + ); + } + + if ( ! isSelected ) { + return ; + } + }; + // Show the loading overlay when: // 1. Player is not ready // 2. Player is loaded but preview is not ready @@ -144,13 +184,14 @@ export default function Player( { isSelected, attributes } ) { return ( - { ! isSelected && } + { renderOverlay() } { showLoadingOverlay && loadingOverlay } { html && ( ) } diff --git a/projects/packages/videopress/src/client/block-editor/blocks/video/components/player/style.native.scss b/projects/packages/videopress/src/client/block-editor/blocks/video/components/player/style.native.scss index 223b9dd49e9ff..bcfcd8cc8b11f 100644 --- a/projects/packages/videopress/src/client/block-editor/blocks/video/components/player/style.native.scss +++ b/projects/packages/videopress/src/client/block-editor/blocks/video/components/player/style.native.scss @@ -11,6 +11,29 @@ z-index: 1; } +.videopress-player__overlay-controls { + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 100%; + z-index: 1; +} + +.videopress-player__overlay-controls-button { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + width: 100%; +} + +.videopress-player__overlay-controls-button-icon { + width: 64px; + height: 64px; + size: 64; +} + .videopress-player__loading { display: flex; justify-content: space-evenly;