Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix video does not stop playing when going to inbox from search #54552

Merged
merged 22 commits into from
Feb 20, 2025
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@ type CarouselItemProps = {

/** Whether the attachment is currently being viewed in the carousel */
isFocused: boolean;

/** The reportID related to the attachment */
reportID?: string;
};

function CarouselItem({item, onPress, isFocused, isModalHovered}: CarouselItemProps) {
function CarouselItem({item, onPress, isFocused, isModalHovered, reportID}: CarouselItemProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const {isAttachmentHidden} = useContext(ReportAttachmentsContext);
Expand Down Expand Up @@ -85,6 +88,7 @@ function CarouselItem({item, onPress, isFocused, isModalHovered}: CarouselItemPr
isFocused={isFocused}
duration={item.duration}
fallbackSource={Expensicons.AttachmentNotFound}
reportID={reportID}
/>
</View>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,13 @@ type AttachmentCarouselPagerProps = {

/** Sets the visibility of the arrows. */
setShouldShowArrows: (show?: SetStateAction<boolean>) => void;

/** The reportID related to the attachment */
reportID?: string;
};

function AttachmentCarouselPager(
{items, activeSource, initialPage, setShouldShowArrows, onPageSelected, onClose}: AttachmentCarouselPagerProps,
{items, activeSource, initialPage, setShouldShowArrows, onPageSelected, onClose, reportID}: AttachmentCarouselPagerProps,
ref: ForwardedRef<AttachmentCarouselPagerHandle>,
) {
const {handleTap, handleScaleChange, isScrollEnabled} = useCarouselContextEvents(setShouldShowArrows);
Expand Down Expand Up @@ -127,6 +130,7 @@ function AttachmentCarouselPager(
<CarouselItem
item={item}
isFocused={index === activePageIndex && activeSource === item.source}
reportID={reportID}
/>
</View>
));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ function AttachmentCarousel({report, source, onNavigate, setDownloadButtonVisibi
onPageSelected={({nativeEvent: {position: newPage}}) => updatePage(newPage)}
onClose={onClose}
ref={pagerRef}
reportID={report.reportID}
/>
</>
)}
Expand Down
7 changes: 4 additions & 3 deletions src/components/Attachments/AttachmentCarousel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
import {canUseTouchScreen as canUseTouchScreenUtil} from '@libs/DeviceCapabilities';
import Navigation from '@libs/Navigation/Navigation';
import variables from '@styles/variables';
import CONST from '@src/CONST';
Expand Down Expand Up @@ -63,7 +63,7 @@ function AttachmentCarousel({report, source, onNavigate, setDownloadButtonVisibi
const pagerRef = useRef<GestureType>(null);
const [parentReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`, {canEvict: false});
const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, {canEvict: false});
const canUseTouchScreen = DeviceCapabilities.canUseTouchScreen();
const canUseTouchScreen = canUseTouchScreenUtil();

const modalStyles = styles.centeredModalStyles(shouldUseNarrowLayout, true);
const cellWidth = useMemo(
Expand Down Expand Up @@ -230,10 +230,11 @@ function AttachmentCarousel({report, source, onNavigate, setDownloadButtonVisibi
isFocused={activeSource === item.source}
onPress={canUseTouchScreen ? handleTap : undefined}
isModalHovered={shouldShowArrows}
reportID={report.reportID}
/>
</View>
),
[activeSource, canUseTouchScreen, cellWidth, handleTap, shouldShowArrows, styles.h100],
[activeSource, canUseTouchScreen, cellWidth, handleTap, report.reportID, shouldShowArrows, styles.h100],
);
/** Pan gesture handing swiping through attachments on touch screen devices */
const pan = useMemo(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ type AttachmentViewVideoProps = Pick<AttachmentViewProps, 'duration' | 'isHovere
source: string;

shouldUseSharedVideoElement?: boolean;

/** The reportID related to the attachment */
reportID?: string;
};

function AttachmentViewVideo({source, isHovered = false, shouldUseSharedVideoElement = false, duration = 0}: AttachmentViewVideoProps) {
function AttachmentViewVideo({source, isHovered = false, shouldUseSharedVideoElement = false, duration = 0, reportID}: AttachmentViewVideoProps) {
const {shouldUseNarrowLayout} = useResponsiveLayout();
const styles = useThemeStyles();

Expand All @@ -22,6 +25,7 @@ function AttachmentViewVideo({source, isHovered = false, shouldUseSharedVideoEle
isVideoHovered={isHovered}
videoDuration={duration}
style={[styles.w100, styles.h100, styles.pb5]}
reportID={reportID}
/>
);
}
Expand Down
13 changes: 8 additions & 5 deletions src/components/Attachments/AttachmentView/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import {Str} from 'expensify-common';
import React, {memo, useContext, useEffect, useState} from 'react';
import React, {memo, useEffect, useState} from 'react';
import type {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native';
import {View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import AttachmentCarouselPagerContext from '@components/Attachments/AttachmentCarousel/Pager/AttachmentCarouselPagerContext';
import type {Attachment, AttachmentSource} from '@components/Attachments/types';
import DistanceEReceipt from '@components/DistanceEReceipt';
import EReceipt from '@components/EReceipt';
Expand All @@ -25,6 +24,7 @@ import {getFileResolution, isHighResolutionImage} from '@libs/fileDownload/FileU
import {hasEReceipt, hasReceiptSource, isDistanceRequest, isPerDiemRequest} from '@libs/TransactionUtils';
import type {ColorValue} from '@styles/utils/types';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import AttachmentViewImage from './AttachmentViewImage';
import AttachmentViewPdf from './AttachmentViewPdf';
Expand Down Expand Up @@ -80,6 +80,9 @@ type AttachmentViewProps = Attachment & {

/** Flag indicating if the attachment is being uploaded. */
isUploading?: boolean;

/** The reportID related to the attachment */
reportID?: string;
};

function AttachmentView({
Expand All @@ -106,11 +109,11 @@ function AttachmentView({
isUploaded = true,
isDeleted,
isUploading = false,
reportID,
}: AttachmentViewProps) {
const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`);
const {translate} = useLocalize();
const {updateCurrentlyPlayingURL} = usePlaybackContext();
const attachmentCarouselPagerContext = useContext(AttachmentCarouselPagerContext);

const theme = useTheme();
const {safeAreaPaddingBottomStyle} = useStyledSafeAreaInsets();
Expand All @@ -120,7 +123,6 @@ function AttachmentView({
const [isHighResolution, setIsHighResolution] = useState<boolean>(false);
const [hasPDFFailedToLoad, setHasPDFFailedToLoad] = useState(false);
const isVideo = (typeof source === 'string' && Str.isVideo(source)) || (file?.name && Str.isVideo(file.name));
const isUsedInCarousel = !!attachmentCarouselPagerContext?.pagerRef;

useEffect(() => {
if (!isFocused && !(file && isUsedInAttachmentModal)) {
Expand Down Expand Up @@ -297,9 +299,10 @@ function AttachmentView({
return (
<AttachmentViewVideo
source={source}
shouldUseSharedVideoElement={isUsedInCarousel}
shouldUseSharedVideoElement={!CONST.ATTACHMENT_LOCAL_URL_PREFIX.some((prefix) => source.startsWith(prefix))}
isHovered={isHovered}
duration={duration}
reportID={reportID}
/>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import {AttachmentContext} from '@components/AttachmentContext';
import {isDeletedNode} from '@components/HTMLEngineProvider/htmlEngineUtils';
import {ShowContextMenuContext} from '@components/ShowContextMenuContext';
import VideoPlayerPreview from '@components/VideoPlayerPreview';
import useCurrentReportID from '@hooks/useCurrentReportID';
import * as FileUtils from '@libs/fileDownload/FileUtils';
import {getFileName} from '@libs/fileDownload/FileUtils';
import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot';
import Navigation from '@navigation/Navigation';
import CONST from '@src/CONST';
Expand All @@ -20,12 +19,11 @@ function VideoRenderer({tnode, key}: VideoRendererProps) {
const htmlAttribs = tnode.attributes;
const attrHref = htmlAttribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE] || htmlAttribs.src || htmlAttribs.href || '';
const sourceURL = tryResolveUrlFromApiRoot(attrHref);
const fileName = FileUtils.getFileName(`${sourceURL}`);
const fileName = getFileName(`${sourceURL}`);
const thumbnailUrl = tryResolveUrlFromApiRoot(htmlAttribs[CONST.ATTACHMENT_THUMBNAIL_URL_ATTRIBUTE]);
const width = Number(htmlAttribs[CONST.ATTACHMENT_THUMBNAIL_WIDTH_ATTRIBUTE]);
const height = Number(htmlAttribs[CONST.ATTACHMENT_THUMBNAIL_HEIGHT_ATTRIBUTE]);
const duration = Number(htmlAttribs[CONST.ATTACHMENT_DURATION_ATTRIBUTE]);
const currentReportIDValue = useCurrentReportID();
const isDeleted = isDeletedNode(tnode);

return (
Expand All @@ -36,7 +34,7 @@ function VideoRenderer({tnode, key}: VideoRendererProps) {
<VideoPlayerPreview
key={key}
videoUrl={sourceURL}
reportID={currentReportIDValue?.currentReportID ?? '-1'}
reportID={report?.reportID}
fileName={fileName}
thumbnailUrl={thumbnailUrl}
videoDimensions={{width, height}}
Expand All @@ -46,7 +44,7 @@ function VideoRenderer({tnode, key}: VideoRendererProps) {
if (!sourceURL || !type) {
return;
}
const route = ROUTES.ATTACHMENTS.getRoute(report?.reportID ?? '-1', type, sourceURL, accountID);
const route = ROUTES.ATTACHMENTS.getRoute(report?.reportID, type, sourceURL, accountID);
Navigation.navigate(route);
}}
/>
Expand Down
13 changes: 9 additions & 4 deletions src/components/VideoPlayer/BaseVideoPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ function BaseVideoPlayer({
// eslint-disable-next-line @typescript-eslint/no-unused-vars
isVideoHovered = false,
isPreview,
reportID,
}: VideoPlayerProps) {
const styles = useThemeStyles();
const {
Expand All @@ -64,6 +65,7 @@ function BaseVideoPlayer({
updateCurrentlyPlayingURL,
videoResumeTryNumberRef,
setCurrentlyPlayingURL,
currentlyPlayingURLReportID,
} = usePlaybackContext();
const {isFullScreenRef} = useFullScreenContext();
const {isOffline} = useNetwork();
Expand Down Expand Up @@ -311,7 +313,9 @@ function BaseVideoPlayer({
if (shouldUseSharedVideoElement || videoPlayerRef.current !== currentVideoPlayerRef.current) {
return;
}
currentVideoPlayerRef.current = null;
currentVideoPlayerRef.current?.setStatusAsync?.({shouldPlay: false, positionMillis: 0}).then(() => {
currentVideoPlayerRef.current = null;
});
},
[currentVideoPlayerRef, shouldUseSharedVideoElement],
);
Expand Down Expand Up @@ -341,13 +345,14 @@ function BaseVideoPlayer({
},
[setCurrentlyPlayingURL, shouldUseSharedVideoElement],
);

// update shared video elements
useEffect(() => {
if (shouldUseSharedVideoElement || url !== currentlyPlayingURL) {
if (shouldUseSharedVideoElement || url !== currentlyPlayingURL || reportID !== currentlyPlayingURLReportID) {
return;
}
shareVideoPlayerElements(videoPlayerRef.current, videoPlayerElementParentRef.current, videoPlayerElementRef.current, isUploading || isFullScreenRef.current);
}, [currentlyPlayingURL, shouldUseSharedVideoElement, shareVideoPlayerElements, url, isUploading, isFullScreenRef]);
}, [currentlyPlayingURL, shouldUseSharedVideoElement, shareVideoPlayerElements, url, isUploading, isFullScreenRef, reportID, currentlyPlayingURLReportID]);

// Call bindFunctions() through the refs to avoid adding it to the dependency array of the DOM mutation effect, as doing so would change the DOM when the functions update.
const bindFunctionsRef = useRef<(() => void) | null>(null);
Expand Down Expand Up @@ -394,7 +399,7 @@ function BaseVideoPlayer({
}
newParentRef.childNodes[0]?.remove();
};
}, [currentVideoPlayerRef, currentlyPlayingURL, isFullScreenRef, originalParent, sharedElement, shouldUseSharedVideoElement, url]);
}, [currentVideoPlayerRef, currentlyPlayingURL, currentlyPlayingURLReportID, isFullScreenRef, originalParent, reportID, sharedElement, shouldUseSharedVideoElement, url]);

useEffect(() => {
if (!shouldPlay) {
Expand Down
1 change: 1 addition & 0 deletions src/components/VideoPlayer/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type VideoPlayerProps = {
controlsStatus?: ValueOf<typeof CONST.VIDEO_PLAYER.CONTROLS_STATUS>;
shouldPlay?: boolean;
isPreview?: boolean;
reportID?: string;
};

export type {VideoPlayerProps, VideoWithOnFullScreenUpdate};
36 changes: 31 additions & 5 deletions src/components/VideoPlayerContexts/PlaybackContext.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import type {NavigationState} from '@react-navigation/native';
import type {AVPlaybackStatus, AVPlaybackStatusToSet} from 'expo-av';
import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react';
import type {View} from 'react-native';
import type {VideoWithOnFullScreenUpdate} from '@components/VideoPlayer/types';
import useCurrentReportID from '@hooks/useCurrentReportID';
import usePrevious from '@hooks/usePrevious';
import isReportTopmostSplitNavigator from '@libs/Navigation/helpers/isReportTopmostSplitNavigator';
import Navigation from '@libs/Navigation/Navigation';
import Visibility from '@libs/Visibility';
import type ChildrenProps from '@src/types/utils/ChildrenProps';
import type {PlaybackContext, StatusCallback} from './types';
Expand All @@ -16,7 +18,7 @@ function PlaybackContextProvider({children}: ChildrenProps) {
const [sharedElement, setSharedElement] = useState<View | HTMLDivElement | null>(null);
const [originalParent, setOriginalParent] = useState<View | HTMLDivElement | null>(null);
const currentVideoPlayerRef = useRef<VideoWithOnFullScreenUpdate | null>(null);
const {currentReportID} = useCurrentReportID() ?? {};
const [currentReportID, setCurrentReportID] = useState<string | undefined>();
const prevCurrentReportID = usePrevious(currentReportID);
const videoResumeTryNumberRef = useRef<number>(0);
const playVideoPromiseRef = useRef<Promise<AVPlaybackStatus>>();
Expand Down Expand Up @@ -48,6 +50,22 @@ function PlaybackContextProvider({children}: ChildrenProps) {
currentVideoPlayerRef.current?.unloadAsync?.();
}, [currentVideoPlayerRef]);

/**
* This function is used to update the currentReportID
* @param state root navigation state
*/
const updateCurrentPlayingReportID = useCallback(
(state: NavigationState) => {
if (!isReportTopmostSplitNavigator()) {
setCurrentReportID(undefined);
return;
}
const reportID = Navigation.getTopmostReportId(state);
setCurrentReportID(reportID);
},
[setCurrentReportID],
);

const updateCurrentlyPlayingURL = useCallback(
(url: string | null) => {
if (currentlyPlayingURL && url !== currentlyPlayingURL) {
Expand Down Expand Up @@ -89,8 +107,9 @@ function PlaybackContextProvider({children}: ChildrenProps) {
setCurrentlyPlayingURL(null);
setSharedElement(null);
setOriginalParent(null);
currentVideoPlayerRef.current = null;
setCurrentlyPlayingURLReportID(undefined);
unloadVideo();
currentVideoPlayerRef.current = null;
});
}, [stopVideo, unloadVideo]);

Expand All @@ -100,10 +119,15 @@ function PlaybackContextProvider({children}: ChildrenProps) {
// This prevents the video that plays when the app opens from being interrupted when currentReportID
// is initially empty or '-1', or when it changes from empty/'-1' to another value
// after the report screen in the central pane is mounted on the large screen.
if (!currentReportID || !prevCurrentReportID || currentReportID === '-1' || prevCurrentReportID === '-1' || currentReportID === prevCurrentReportID) {
if ((!currentReportID && isReportTopmostSplitNavigator()) || (!prevCurrentReportID && !isReportTopmostSplitNavigator()) || currentReportID === prevCurrentReportID) {
return;
}
resetVideoPlayerData();

// We call another setStatusAsync inside useLayoutEffect on the video component,
// so we add a delay here to prevent the error from appearing.
setTimeout(() => {
resetVideoPlayerData();
}, 0);
}, [currentReportID, prevCurrentReportID, resetVideoPlayerData]);

useEffect(() => {
Expand Down Expand Up @@ -131,6 +155,7 @@ function PlaybackContextProvider({children}: ChildrenProps) {
pauseVideo,
checkVideoPlaying,
videoResumeTryNumberRef,
updateCurrentPlayingReportID,
}),
[
updateCurrentlyPlayingURL,
Expand All @@ -143,6 +168,7 @@ function PlaybackContextProvider({children}: ChildrenProps) {
pauseVideo,
checkVideoPlaying,
setCurrentlyPlayingURL,
updateCurrentPlayingReportID,
],
);
return <Context.Provider value={contextValue}>{children}</Context.Provider>;
Expand Down
2 changes: 2 additions & 0 deletions src/components/VideoPlayerContexts/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type {NavigationState} from '@react-navigation/native';
import type {MutableRefObject} from 'react';
import type {View} from 'react-native';
import type {SharedValue} from 'react-native-reanimated';
Expand All @@ -20,6 +21,7 @@ type PlaybackContext = {
pauseVideo: () => void;
checkVideoPlaying: (statusCallback: StatusCallback) => void;
setCurrentlyPlayingURL: React.Dispatch<React.SetStateAction<string | null>>;
updateCurrentPlayingReportID: (state: NavigationState) => void;
};

type VolumeContext = {
Expand Down
3 changes: 2 additions & 1 deletion src/components/VideoPlayerPreview/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type VideoPlayerPreviewProps = {
videoUrl: string;

/** reportID of the video */
reportID: string;
reportID: string | undefined;

/** Dimension of a video. */
videoDimensions: VideoDimensions;
Expand Down Expand Up @@ -91,6 +91,7 @@ function VideoPlayerPreview({videoUrl, thumbnailUrl, reportID, fileName, videoDi
style={[styles.w100, styles.h100]}
isPreview
videoPlayerStyle={styles.videoPlayerPreview}
reportID={reportID}
/>
<View style={[styles.pAbsolute, styles.w100]}>
<IconButton
Expand Down
Loading