Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/components/Attachments/AttachmentView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import Icon from '@components/Icon';
import PerDiemEReceipt from '@components/PerDiemEReceipt';
import ScrollView from '@components/ScrollView';
import Text from '@components/Text';
import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext';
import {usePlaybackActionsContext, usePlaybackStateContext} from '@components/VideoPlayerContexts/PlaybackContext';
import useFirstRenderRoute from '@hooks/useFirstRenderRoute';
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
Expand Down Expand Up @@ -135,7 +135,8 @@ function AttachmentView({
const [transactionFromOnyx] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${getNonEmptyStringOnyxID(transactionID)}`, {canBeMissing: true});
const transaction = transactionProp ?? transactionFromOnyx;
const {translate} = useLocalize();
const {updateCurrentURLAndReportID, currentlyPlayingURL, playVideo} = usePlaybackContext();
const {currentlyPlayingURL} = usePlaybackStateContext();
const {updateCurrentURLAndReportID, playVideo} = usePlaybackActionsContext();

const attachmentCarouselPagerContext = useContext(AttachmentCarouselPagerContext);
const {onAttachmentError, onTap} = attachmentCarouselPagerContext ?? {};
Expand Down
20 changes: 3 additions & 17 deletions src/components/VideoPlayer/BaseVideoPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import Hoverable from '@components/Hoverable';
import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback';
import {useFullScreenContext} from '@components/VideoPlayerContexts/FullScreenContext';
import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext';
import {usePlaybackActionsContext, usePlaybackStateContext} from '@components/VideoPlayerContexts/PlaybackContext';
import type {PlaybackSpeed} from '@components/VideoPlayerContexts/types';
import {useVideoPopoverMenuContext} from '@components/VideoPlayerContexts/VideoPopoverMenuContext';
import {useVolumeContext} from '@components/VideoPlayerContexts/VolumeContext';
Expand Down Expand Up @@ -53,22 +53,8 @@ function BaseVideoPlayer({
onTap,
}: VideoPlayerProps & {reportID: string}) {
const styles = useThemeStyles();
const {
pauseVideo,
playVideo,
replayVideo,
currentlyPlayingURL,
sharedElement,
originalParent,
shareVideoPlayerElements,
currentVideoPlayerRef,
currentVideoViewRef,
updateCurrentURLAndReportID,
setCurrentlyPlayingURL,
mountedVideoPlayersRef,
playerStatus,
updatePlayerStatus,
} = usePlaybackContext();
const {currentlyPlayingURL, sharedElement, originalParent, currentVideoPlayerRef, currentVideoViewRef, mountedVideoPlayersRef, playerStatus} = usePlaybackStateContext();
const {pauseVideo, playVideo, replayVideo, shareVideoPlayerElements, updateCurrentURLAndReportID, setCurrentlyPlayingURL, updatePlayerStatus} = usePlaybackActionsContext();
const {isFullScreenRef} = useFullScreenContext();

const isOffline = useNetwork().isOffline;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type {GestureStateChangeEvent, GestureUpdateEvent, PanGestureChangeEventP
import {Gesture, GestureDetector} from 'react-native-gesture-handler';
import Animated, {useAnimatedStyle, useSharedValue} from 'react-native-reanimated';
import {scheduleOnRN} from 'react-native-worklets';
import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext';
import {usePlaybackActionsContext} from '@components/VideoPlayerContexts/PlaybackContext';
import useThemeStyles from '@hooks/useThemeStyles';

type ProgressBarProps = {
Expand All @@ -24,7 +24,7 @@ function getProgress(currentPosition: number, maxPosition: number): number {

function ProgressBar({duration, position, seekPosition}: ProgressBarProps) {
const styles = useThemeStyles();
const {pauseVideo, playVideo, checkIfVideoIsPlaying} = usePlaybackContext();
const {pauseVideo, playVideo, checkIfVideoIsPlaying} = usePlaybackActionsContext();
const [sliderWidth, setSliderWidth] = useState(1);
const [isSliderPressed, setIsSliderPressed] = useState(false);
const progressWidth = useSharedValue(0);
Expand Down
4 changes: 2 additions & 2 deletions src/components/VideoPlayer/VideoPlayerControls/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type {ValueOf} from 'type-fest';
import Text from '@components/Text';
import IconButton from '@components/VideoPlayer/IconButton';
import {convertSecondsToTime} from '@components/VideoPlayer/utils';
import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext';
import {usePlaybackActionsContext} from '@components/VideoPlayerContexts/PlaybackContext';
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
Expand Down Expand Up @@ -69,7 +69,7 @@ function VideoPlayerControls({
const icons = useMemoizedLazyExpensifyIcons(['ThreeDots', 'Pause', 'Play', 'Fullscreen']);
const styles = useThemeStyles();
const {translate} = useLocalize();
const {updateCurrentURLAndReportID} = usePlaybackContext();
const {updateCurrentURLAndReportID} = usePlaybackActionsContext();
const [shouldShowTime, setShouldShowTime] = useState(false);
const iconSpacing = small ? styles.mr3 : styles.mr4;

Expand Down
105 changes: 53 additions & 52 deletions src/components/VideoPlayerContexts/PlaybackContext/index.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import type {VideoPlayer, VideoPlayerStatus, VideoView} from 'expo-video';
import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react';
import React, {useCallback, useContext, useEffect, useRef, useState} from 'react';
import type {View} from 'react-native';
import {getReportOrDraftReport, isChatThread} from '@libs/ReportUtils';
import Navigation from '@navigation/Navigation';
import type ChildrenProps from '@src/types/utils/ChildrenProps';
import type {ProtectedCurrentRouteReportID} from './playbackContextReportIDUtils';
import {findURLInReportOrAncestorAttachments, getCurrentRouteReportID, NO_REPORT_ID, NO_REPORT_ID_IN_PARAMS, normalizeReportID} from './playbackContextReportIDUtils';
import type {OriginalParent, PlaybackContext, PlaybackContextValues} from './types';
import type {OriginalParent, PlaybackActionsContext, PlaybackActionsContextValues, PlaybackStateContext, PlaybackStateContextValues} from './types';
import usePlaybackContextVideoRefs from './usePlaybackContextVideoRefs';

const Context = React.createContext<PlaybackContext | null>(null);
const ContextState = React.createContext<PlaybackStateContext | null>(null);
const ContextActions = React.createContext<PlaybackActionsContext | null>(null);

function PlaybackContextProvider({children}: ChildrenProps) {
const [currentlyPlayingURL, setCurrentlyPlayingURL] = useState<PlaybackContextValues['currentlyPlayingURL']>(null);
const [sharedElement, setSharedElement] = useState<PlaybackContextValues['sharedElement']>(null);
const [currentlyPlayingURL, setCurrentlyPlayingURL] = useState<PlaybackStateContextValues['currentlyPlayingURL']>(null);
const [sharedElement, setSharedElement] = useState<PlaybackStateContextValues['sharedElement']>(null);
const [originalParent, setOriginalParent] = useState<OriginalParent>(null);
const [currentRouteReportID, setCurrentRouteReportID] = useState<ProtectedCurrentRouteReportID>(NO_REPORT_ID);
const mountedVideoPlayersRef = useRef<string[]>([]);
Expand All @@ -28,7 +29,7 @@ function PlaybackContextProvider({children}: ChildrenProps) {

const video = usePlaybackContextVideoRefs(resetContextProperties);

const updateCurrentURLAndReportID: PlaybackContextValues['updateCurrentURLAndReportID'] = useCallback(
const updateCurrentURLAndReportID: PlaybackActionsContextValues['updateCurrentURLAndReportID'] = useCallback(
(url, reportID) => {
if (!reportID) {
return;
Expand Down Expand Up @@ -69,7 +70,7 @@ function PlaybackContextProvider({children}: ChildrenProps) {
playerStatus.current = newStatus;
}, []);

const shareVideoPlayerElements: PlaybackContextValues['shareVideoPlayerElements'] = useCallback(
const shareVideoPlayerElements: PlaybackActionsContextValues['shareVideoPlayerElements'] = useCallback(
(
videoPlayerRef: VideoPlayer | null,
videoViewRef: VideoView | null,
Expand Down Expand Up @@ -117,55 +118,55 @@ function PlaybackContextProvider({children}: ChildrenProps) {
});
}, [currentRouteReportID, currentlyPlayingURL, video, video.resetPlayerData]);

const contextValue: PlaybackContext = useMemo(
() => ({
updateCurrentURLAndReportID,
currentlyPlayingURL,
currentRouteReportID: normalizeReportID(currentRouteReportID),
originalParent,
sharedElement,
shareVideoPlayerElements,
setCurrentlyPlayingURL,
currentVideoPlayerRef: video.playerRef,
currentVideoViewRef: video.viewRef,
playVideo: video.play,
pauseVideo: video.pause,
replayVideo: video.replay,
stopVideo: video.stop,
checkIfVideoIsPlaying: video.isPlaying,
resetVideoPlayerData: video.resetPlayerData,
mountedVideoPlayersRef,
playerStatus,
updatePlayerStatus,
}),
[
updateCurrentURLAndReportID,
currentlyPlayingURL,
currentRouteReportID,
originalParent,
sharedElement,
shareVideoPlayerElements,
video.playerRef,
video.viewRef,
video.play,
video.pause,
video.replay,
video.stop,
video.isPlaying,
video.resetPlayerData,
updatePlayerStatus,
],
// Because of the React Compiler we don't need to memoize it manually
// eslint-disable-next-line react/jsx-no-constructed-context-values
const stateValue: PlaybackStateContext = {
currentlyPlayingURL,
currentRouteReportID: normalizeReportID(currentRouteReportID),
originalParent,
sharedElement,
currentVideoPlayerRef: video.playerRef,
currentVideoViewRef: video.viewRef,
mountedVideoPlayersRef,
playerStatus,
};

// Because of the React Compiler we don't need to memoize it manually
// eslint-disable-next-line react/jsx-no-constructed-context-values
const actionsValue: PlaybackActionsContext = {
updateCurrentURLAndReportID,
shareVideoPlayerElements,
setCurrentlyPlayingURL,
playVideo: video.play,
pauseVideo: video.pause,
replayVideo: video.replay,
stopVideo: video.stop,
checkIfVideoIsPlaying: video.isPlaying,
resetVideoPlayerData: video.resetPlayerData,
updatePlayerStatus,
};

return (
<ContextState.Provider value={stateValue}>
<ContextActions.Provider value={actionsValue}>{children}</ContextActions.Provider>
</ContextState.Provider>
);
}

return <Context.Provider value={contextValue}>{children}</Context.Provider>;
function usePlaybackStateContext() {
const playbackStateContext = useContext(ContextState);
if (!playbackStateContext) {
throw new Error('usePlaybackStateContext must be used within a PlaybackContextProvider');
}
return playbackStateContext;
}

function usePlaybackContext() {
const playbackContext = useContext(Context);
if (!playbackContext) {
throw new Error('usePlaybackContext must be used within a PlaybackContextProvider');
function usePlaybackActionsContext() {
const playbackActionsContext = useContext(ContextActions);
if (!playbackActionsContext) {
throw new Error('usePlaybackActionsContext must be used within a PlaybackContextProvider');
}
return playbackContext;
return playbackActionsContext;
}

export {Context as PlaybackContext, PlaybackContextProvider, usePlaybackContext};
export {ContextActions as PlaybackActionsContext, ContextState as PlaybackStateContext, PlaybackContextProvider, usePlaybackStateContext, usePlaybackActionsContext};
60 changes: 34 additions & 26 deletions src/components/VideoPlayerContexts/PlaybackContext/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,7 @@ type VideoElementData = {
reportID: string | undefined;
};

/**
* Playback-related context values available throughout the app.
*/
type PlaybackContextValues = {
/**
* Updates the currently tracked video URL and associated report ID.
* @param url The new video URL.
* @param reportID The new report ID.
*/
updateCurrentURLAndReportID: (url: string | undefined, reportID: string | undefined) => void;

type PlaybackStateContextValues = {
/**
* The URL of the video currently being played, or null if none.
*/
Expand All @@ -70,6 +60,28 @@ type PlaybackContextValues = {
*/
sharedElement: View | HTMLDivElement | null;

/**
* Array of currently mounted Video Player instances
*/
mountedVideoPlayersRef: RefObject<string[]>;

/**
* Status of the currently used Video Player
*/
playerStatus: RefObject<VideoPlayerStatus>;
};

/**
* Playback-related context actions values available throughout the app.
*/
type PlaybackActionsContextValues = {
/**
* Updates the currently tracked video URL and associated report ID.
* @param url The new video URL.
* @param reportID The new report ID.
*/
updateCurrentURLAndReportID: (url: string | undefined, reportID: string | undefined) => void;

/**
* Updates shared video player elements across different parts of the UI.
* @param playerRef Reference to the VideoPlayer instance.
Expand All @@ -93,16 +105,6 @@ type PlaybackContextValues = {
*/
setCurrentlyPlayingURL: React.Dispatch<React.SetStateAction<string | null>>;

/**
* Array of currently mounted Video Player instances
*/
mountedVideoPlayersRef: RefObject<string[]>;

/**
* Status of the currently used Video Player
*/
playerStatus: RefObject<VideoPlayerStatus>;

/**
* Updates current videoPlayer status
* @param newStatus New videoPlayer status
Expand Down Expand Up @@ -164,17 +166,23 @@ type PlaybackContextVideoRefs = {
};

/**
* Combined playback context with values and video control helpers.
* Combined playback state context with values and video control helpers.
*/
type PlaybackContext = PlaybackContextValues & {
type PlaybackStateContext = PlaybackStateContextValues & {
currentVideoPlayerRef: PlaybackContextVideoRefs['playerRef'];
currentVideoViewRef: PlaybackContextVideoRefs['viewRef'];
};

/**
* Combined playback actions context with values and video control helpers.
*/
type PlaybackActionsContext = PlaybackActionsContextValues & {
resetVideoPlayerData: PlaybackContextVideoRefs['resetPlayerData'];
playVideo: PlaybackContextVideoRefs['play'];
pauseVideo: PlaybackContextVideoRefs['pause'];
replayVideo: PlaybackContextVideoRefs['replay'];
stopVideo: PlaybackContextVideoRefs['stop'];
checkIfVideoIsPlaying: PlaybackContextVideoRefs['isPlaying'];
currentVideoPlayerRef: PlaybackContextVideoRefs['playerRef'];
currentVideoViewRef: PlaybackContextVideoRefs['viewRef'];
};

export type {PlaybackContextVideoRefs, StopVideo, PlaybackContextValues, PlaybackContext, OriginalParent};
export type {PlaybackContextVideoRefs, StopVideo, PlaybackStateContextValues, PlaybackActionsContextValues, PlaybackStateContext, PlaybackActionsContext, OriginalParent};
4 changes: 2 additions & 2 deletions src/components/VideoPlayerContexts/VolumeContext.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import React, {useCallback, useContext, useEffect, useMemo} from 'react';
import {useSharedValue} from 'react-native-reanimated';
import type ChildrenProps from '@src/types/utils/ChildrenProps';
import {usePlaybackContext} from './PlaybackContext';
import {usePlaybackStateContext} from './PlaybackContext';
import type {VolumeContext} from './types';

const Context = React.createContext<VolumeContext | null>(null);

function VolumeContextProvider({children}: ChildrenProps) {
const {currentVideoPlayerRef, originalParent} = usePlaybackContext();
const {currentVideoPlayerRef, originalParent} = usePlaybackStateContext();
const volume = useSharedValue(0);
// We need this field to remember the last value before clicking mute
const lastNonZeroVolume = useSharedValue(1);
Expand Down
5 changes: 3 additions & 2 deletions src/components/VideoPlayerPreview/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {View} from 'react-native';
import {useIsOnSearch} from '@components/Search/SearchScopeProvider';
import VideoPlayer from '@components/VideoPlayer';
import IconButton from '@components/VideoPlayer/IconButton';
import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext';
import {usePlaybackActionsContext, usePlaybackStateContext} from '@components/VideoPlayerContexts/PlaybackContext';
import useCheckIfRouteHasRemainedUnchanged from '@hooks/useCheckIfRouteHasRemainedUnchanged';
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
Expand Down Expand Up @@ -52,7 +52,8 @@ function VideoPlayerPreview({videoUrl, thumbnailUrl, reportID, fileName, videoDi
const icons = useMemoizedLazyExpensifyIcons(['Expand']);
const styles = useThemeStyles();
const {translate} = useLocalize();
const {currentlyPlayingURL, currentRouteReportID, updateCurrentURLAndReportID} = usePlaybackContext();
const {currentlyPlayingURL, currentRouteReportID} = usePlaybackStateContext();
const {updateCurrentURLAndReportID} = usePlaybackActionsContext();

/* This needs to be isSmallScreenWidth because we want to be able to play video in chat (not in attachment modal) when preview is inside an RHP */
// eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
Expand Down
4 changes: 2 additions & 2 deletions src/pages/Search/SearchPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {ScrollOffsetContext} from '@components/ScrollOffsetContextProvider';
import {useSearchContext} from '@components/Search/SearchContext';
import type {SearchHeaderOptionValue} from '@components/Search/SearchPageHeader/SearchPageHeader';
import type {PaymentData, SearchParams} from '@components/Search/types';
import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext';
import {usePlaybackActionsContext} from '@components/VideoPlayerContexts/PlaybackContext';
import useAllTransactions from '@hooks/useAllTransactions';
import useBulkPayOptions from '@hooks/useBulkPayOptions';
import useConfirmModal from '@hooks/useConfirmModal';
Expand Down Expand Up @@ -1165,7 +1165,7 @@ function SearchPage({route}: SearchPageProps) {
validateFiles(files, Array.from(e.dataTransfer?.items ?? []));
};

const {resetVideoPlayerData} = usePlaybackContext();
const {resetVideoPlayerData} = usePlaybackActionsContext();

const metadata = searchResults?.search;
const shouldShowFooter = !!metadata?.count || selectedTransactionsKeys.length > 0;
Expand Down
Loading