Skip to content

Commit

Permalink
Merge pull request #54552 from mkzie2/mkzie2-issue/54166
Browse files Browse the repository at this point in the history
fix video does not stop playing when going to inbox from search
  • Loading branch information
aldo-expensify authored Feb 20, 2025
2 parents 7b6942e + c7ba75b commit a4f62fa
Show file tree
Hide file tree
Showing 13 changed files with 80 additions and 27 deletions.
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

0 comments on commit a4f62fa

Please sign in to comment.