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;