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 sharing #38407

Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b307865
Add window dimensions blocking POC
Skalakid Mar 15, 2024
344c68d
Enhance bug fix code structure
Skalakid Mar 15, 2024
deec3ab
Merge branch 'main' into @Skalakid/fix-video-element-sharing
Skalakid Mar 18, 2024
4188a33
Fix TS errors
Skalakid Mar 18, 2024
29a802a
Fix video sharing bug when changing window dimensions while video is …
Skalakid Mar 18, 2024
4bbae6c
Restore code that is used in other places
Skalakid Mar 18, 2024
cab0496
Fix video starting playing when exiting fullscreen with paused video
Skalakid Mar 18, 2024
f54eb12
Change fix structure
Skalakid Mar 18, 2024
05ed715
Merge branch 'main' into @Skalakid/fix-video-element-sharing
Skalakid Mar 18, 2024
1f04efc
Add locked screen dimensions updating when orientation changes
Skalakid Mar 19, 2024
82ca579
Enhance device orientation checking
Skalakid Mar 19, 2024
59ff4a2
Fix video pausing when dismissing fullscreen mode
Skalakid Mar 19, 2024
4e06538
Change orginal size detction
Skalakid Mar 19, 2024
1ca5515
Add comment
Skalakid Mar 19, 2024
72b0fb1
Merge branch 'main' into @Skalakid/fix-video-element-sharing
Skalakid Mar 20, 2024
df3db7d
Merge branch 'main' into @Skalakid/fix-video-element-sharing
Skalakid Mar 20, 2024
ac35413
Add review changes
Skalakid Mar 20, 2024
3f75fe3
Fix fullscreen mode bug
Skalakid Mar 20, 2024
5b35c6e
Merge branch 'main' into @Skalakid/fix-video-element-sharing
Skalakid Mar 21, 2024
5622588
Fix fullscreen context types
Skalakid Mar 21, 2024
328fbd8
Merge branch 'main' into @Skalakid/fix-video-element-sharing
Skalakid Mar 22, 2024
e75f1c4
Add review changes
Skalakid Mar 22, 2024
75896d2
Fix video pausing in fullscreen mode
Skalakid Mar 25, 2024
9facbb7
Merge branch 'main' into @Skalakid/fix-video-element-sharing
Skalakid Mar 26, 2024
47f92d3
Merge branch 'main' into @Skalakid/fix-video-element-sharing
Skalakid Mar 27, 2024
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
2 changes: 2 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import SafeArea from './components/SafeArea';
import ThemeIllustrationsProvider from './components/ThemeIllustrationsProvider';
import ThemeProvider from './components/ThemeProvider';
import ThemeStylesProvider from './components/ThemeStylesProvider';
import {FullScreenContextProvider} from './components/VideoPlayerContexts/FullScreenContext';
import {PlaybackContextProvider} from './components/VideoPlayerContexts/PlaybackContext';
import {VideoPopoverMenuContextProvider} from './components/VideoPlayerContexts/VideoPopoverMenuContext';
import {VolumeContextProvider} from './components/VideoPlayerContexts/VolumeContext';
Expand Down Expand Up @@ -76,6 +77,7 @@ function App({url}: AppProps) {
ActiveElementRoleProvider,
ActiveWorkspaceContextProvider,
PlaybackContextProvider,
FullScreenContextProvider,
VolumeContextProvider,
VideoPopoverMenuContextProvider,
]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ function CarouselItem({item, onPress, isFocused, isModalHovered}) {
isHovered={isModalHovered}
isFocused={isFocused}
optionalVideoDuration={item.duration}
isUsedInCarousel
/>
</View>

Expand Down
20 changes: 17 additions & 3 deletions src/components/Attachments/AttachmentCarousel/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
import BlockingView from '@components/BlockingViews/BlockingView';
import * as Illustrations from '@components/Icon/Illustrations';
import {useFullScreenContext} from '@components/VideoPlayerContexts/FullScreenContext';
import withLocalize from '@components/withLocalize';
import withWindowDimensions from '@components/withWindowDimensions';
import useTheme from '@hooks/useTheme';
Expand Down Expand Up @@ -32,6 +33,7 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source,
const theme = useTheme();
const styles = useThemeStyles();
const scrollRef = useRef(null);
const {isFullscreenRef} = useFullScreenContext();

const canUseTouchScreen = DeviceCapabilities.canUseTouchScreen();

Expand Down Expand Up @@ -77,6 +79,10 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source,
*/
const updatePage = useCallback(
({viewableItems}) => {
if (isFullscreenRef.current) {
return;
}

Keyboard.dismiss();

// Since we can have only one item in view at a time, we can use the first item in the array
Expand All @@ -92,7 +98,7 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source,

onNavigate(entry.item);
},
[onNavigate],
[isFullscreenRef, onNavigate],
);

/**
Expand All @@ -101,6 +107,9 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source,
*/
const cycleThroughAttachments = useCallback(
(deltaSlide) => {
if (isFullscreenRef.current) {
return;
}
const nextIndex = page + deltaSlide;
const nextItem = attachments[nextIndex];

Expand All @@ -110,7 +119,7 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source,

scrollRef.current.scrollToIndex({index: nextIndex, animated: canUseTouchScreen});
},
[attachments, canUseTouchScreen, page],
[attachments, canUseTouchScreen, isFullscreenRef, page],
);

/**
Expand Down Expand Up @@ -157,7 +166,12 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source,
return (
<View
style={[styles.flex1, styles.attachmentCarouselContainer]}
onLayout={({nativeEvent}) => setContainerWidth(PixelRatio.roundToNearestPixel(nativeEvent.layout.width))}
onLayout={({nativeEvent}) => {
if (isFullscreenRef.current) {
return;
}
setContainerWidth(PixelRatio.roundToNearestPixel(nativeEvent.layout.width));
}}
onMouseEnter={() => !canUseTouchScreen && setShouldShowArrows(true)}
onMouseLeave={() => !canUseTouchScreen && setShouldShowArrows(false)}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ const defaultProps = {
};

function AttachmentViewVideo({source, isHovered, shouldUseSharedVideoElement, videoDuration}) {
const {isSmallScreen} = useWindowDimensions();
const {isSmallScreenWidth} = useWindowDimensions();
const styles = useThemeStyles();

return (
<VideoPlayer
url={source}
shouldUseSharedVideoElement={shouldUseSharedVideoElement && !isSmallScreen}
shouldUseSharedVideoElement={shouldUseSharedVideoElement && !isSmallScreenWidth}
isVideoHovered={isHovered}
videoDuration={videoDuration}
style={[styles.w100, styles.h100]}
Expand Down
83 changes: 42 additions & 41 deletions src/components/VideoPlayer/BaseVideoPlayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,18 @@ import _ from 'underscore';
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 VideoPopoverMenu from '@components/VideoPopoverMenu';
import useThemeStyles from '@hooks/useThemeStyles';
import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL';
import * as Browser from '@libs/Browser';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
import CONST from '@src/CONST';
import {videoPlayerDefaultProps, videoPlayerPropTypes} from './propTypes';
import shouldReplayVideo from './shouldReplayVideo';
import * as VideoUtils from './utils';
import VideoPlayerControls from './VideoPlayerControls';

const isMobileSafari = Browser.isMobileSafari();

function BaseVideoPlayer({
url,
resizeMode,
Expand All @@ -43,8 +41,19 @@ function BaseVideoPlayer({
isVideoHovered,
}) {
const styles = useThemeStyles();
const {pauseVideo, playVideo, currentlyPlayingURL, updateSharedElements, sharedElement, originalParent, shareVideoPlayerElements, currentVideoPlayerRef, updateCurrentlyPlayingURL} =
usePlaybackContext();
const {
pauseVideo,
playVideo,
currentlyPlayingURL,
updateSharedElements,
sharedElement,
originalParent,
shareVideoPlayerElements,
currentVideoPlayerRef,
updateCurrentlyPlayingURL,
videoResumeTryNumber,
} = usePlaybackContext();
const {isFullscreenRef} = useFullScreenContext();
const [duration, setDuration] = useState(videoDuration * 1000);
const [position, setPosition] = useState(0);
const [isPlaying, setIsPlaying] = useState(false);
Expand All @@ -58,11 +67,10 @@ function BaseVideoPlayer({
const videoPlayerElementParentRef = useRef(null);
const videoPlayerElementRef = useRef(null);
const sharedVideoPlayerParentRef = useRef(null);
const videoResumeTryNumber = useRef(0);
const canUseTouchScreen = DeviceCapabilities.canUseTouchScreen();
const isCurrentlyURLSet = currentlyPlayingURL === url;
const isUploading = _.some(CONST.ATTACHMENT_LOCAL_URL_PREFIX, (prefix) => url.startsWith(prefix));
const shouldUseSharedVideoElementRef = useRef(shouldUseSharedVideoElement);
const videoStateRef = useRef(null);

const togglePlayCurrentVideo = useCallback(() => {
videoResumeTryNumber.current = 0;
Expand All @@ -73,7 +81,7 @@ function BaseVideoPlayer({
} else {
playVideo();
}
}, [isCurrentlyURLSet, isPlaying, pauseVideo, playVideo, updateCurrentlyPlayingURL, url]);
}, [isCurrentlyURLSet, isPlaying, pauseVideo, playVideo, updateCurrentlyPlayingURL, url, videoResumeTryNumber]);

const showPopoverMenu = (e) => {
setPopoverAnchorPosition({horizontal: e.nativeEvent.pageX, vertical: e.nativeEvent.pageY});
Expand All @@ -95,7 +103,7 @@ function BaseVideoPlayer({
}
videoResumeTryNumber.current -= 1;
},
[playVideo],
[playVideo, videoResumeTryNumber],
);

const handlePlaybackStatusUpdate = useCallback(
Expand All @@ -114,7 +122,7 @@ function BaseVideoPlayer({
setIsBuffering(e.isBuffering || false);
setDuration(currentDuration);
setPosition(currentPositon);

videoStateRef.current = e;
onPlaybackStatusUpdate(e);
},
[onPlaybackStatusUpdate, preventPausingWhenExitingFullscreen, videoDuration],
Expand All @@ -123,20 +131,20 @@ function BaseVideoPlayer({
const handleFullscreenUpdate = useCallback(
(e) => {
onFullscreenUpdate(e);

// fix for iOS native and mWeb: when switching to fullscreen and then exiting
// the fullscreen mode while playing, the video pauses
if (!isPlaying || e.fullscreenUpdate !== VideoFullscreenUpdate.PLAYER_DID_DISMISS) {
return;
}

if (isMobileSafari) {
pauseVideo();
if (e.fullscreenUpdate === VideoFullscreenUpdate.PLAYER_DID_DISMISS) {
isFullscreenRef.current = false;
// we need to use video state ref to check if video is playing, to catch proper state after exiting fullscreen
// and also fix a bug with fullscreen mode dismissing when handleFullscreenUpdate function changes
if (videoStateRef.current && videoStateRef.current.isPlaying) {
pauseVideo();
playVideo();
videoResumeTryNumber.current = 3;
}
}
playVideo();
videoResumeTryNumber.current = 3;
},
[isPlaying, onFullscreenUpdate, pauseVideo, playVideo],
[isFullscreenRef, onFullscreenUpdate, pauseVideo, playVideo, videoResumeTryNumber],
);

const bindFunctions = useCallback(() => {
Expand All @@ -150,45 +158,37 @@ function BaseVideoPlayer({
}, [currentVideoPlayerRef, handleFullscreenUpdate, handlePlaybackStatusUpdate]);

useEffect(() => {
if (!isUploading) {
if (!isUploading || !videoPlayerRef.current) {
return;
}

// If we are uploading a new video, we want to immediately set the video player ref.
currentVideoPlayerRef.current = videoPlayerRef.current;
}, [url, currentVideoPlayerRef, isUploading]);

useEffect(() => {
shouldUseSharedVideoElementRef.current = shouldUseSharedVideoElement;
}, [shouldUseSharedVideoElement]);

useEffect(
() => () => {
if (shouldUseSharedVideoElementRef.current) {
return;
}

// If it's not a shared video player, clear the video player ref.
currentVideoPlayerRef.current = null;
},
[currentVideoPlayerRef],
);

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

// append shared video element to new parent (used for example in attachment modal)
useEffect(() => {
if (url !== currentlyPlayingURL || !sharedElement || !shouldUseSharedVideoElement) {
if (url !== currentlyPlayingURL || !sharedElement || isFullscreenRef.current) {
return;
}

const newParentRef = sharedVideoPlayerParentRef.current;

if (!shouldUseSharedVideoElement) {
if (newParentRef && newParentRef.childNodes[0] && newParentRef.childNodes[0].remove) {
newParentRef.childNodes[0].remove();
}
return;
}

videoPlayerRef.current = currentVideoPlayerRef.current;
if (currentlyPlayingURL === url) {
newParentRef.appendChild(sharedElement);
Expand All @@ -198,9 +198,10 @@ function BaseVideoPlayer({
if (!originalParent && !newParentRef.childNodes[0]) {
return;
}
newParentRef.childNodes[0].remove();
originalParent.appendChild(sharedElement);
};
}, [bindFunctions, currentVideoPlayerRef, currentlyPlayingURL, originalParent, sharedElement, shouldUseSharedVideoElement, url]);
}, [bindFunctions, currentVideoPlayerRef, currentlyPlayingURL, isFullscreenRef, originalParent, sharedElement, shouldUseSharedVideoElement, url]);

return (
<>
Expand Down
5 changes: 4 additions & 1 deletion src/components/VideoPlayer/VideoPlayerControls/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import refPropTypes from '@components/refPropTypes';
import Text from '@components/Text';
import IconButton from '@components/VideoPlayer/IconButton';
import {convertMillisecondsToTime} from '@components/VideoPlayer/utils';
import {useFullScreenContext} from '@components/VideoPlayerContexts/FullScreenContext';
import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
Expand Down Expand Up @@ -42,6 +43,7 @@ const defaultProps = {
};

function VideoPlayerControls({duration, position, url, videoPlayerRef, isPlaying, small, style, showPopoverMenu, togglePlayCurrentVideo}) {
const {isFullscreenRef} = useFullScreenContext();
const styles = useThemeStyles();
const {translate} = useLocalize();
const {updateCurrentlyPlayingURL} = usePlaybackContext();
Expand All @@ -53,9 +55,10 @@ function VideoPlayerControls({duration, position, url, videoPlayerRef, isPlaying
};

const enterFullScreenMode = useCallback(() => {
isFullscreenRef.current = true;
updateCurrentlyPlayingURL(url);
videoPlayerRef.current.presentFullscreenPlayer();
}, [updateCurrentlyPlayingURL, url, videoPlayerRef]);
}, [isFullscreenRef, updateCurrentlyPlayingURL, url, videoPlayerRef]);

const seekPosition = useCallback(
(newPosition) => {
Expand Down
34 changes: 34 additions & 0 deletions src/components/VideoPlayerContexts/FullScreenContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React, {useCallback, useContext, useMemo, useRef} from 'react';
import type WindowDimensions from '@hooks/useWindowDimensions/types';
import type ChildrenProps from '@src/types/utils/ChildrenProps';
import type {FullScreenContext} from './types';

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

function FullScreenContextProvider({children}: ChildrenProps) {
const isFullscreenRef = useRef(false);
Skalakid marked this conversation as resolved.
Show resolved Hide resolved
const lockedWindowDimensionsRef = useRef<WindowDimensions | null>(null);

const lockWindowDimensions = useCallback((newWindowDimensions: WindowDimensions) => {
lockedWindowDimensionsRef.current = newWindowDimensions;
}, []);

const unlockWindowDimensions = useCallback(() => {
lockedWindowDimensionsRef.current = null;
}, []);

const contextValue = useMemo(() => ({isFullscreenRef, lockedWindowDimensionsRef, lockWindowDimensions, unlockWindowDimensions}), [lockWindowDimensions, unlockWindowDimensions]);
return <Context.Provider value={contextValue}>{children}</Context.Provider>;
}

function useFullScreenContext() {
const context = useContext(Context);
if (context === undefined) {
throw new Error('usePlaybackContext must be used within a FullScreenContextProvider');
}
return context;
}

FullScreenContextProvider.displayName = 'FullScreenContextProvider';

export {Context as FullScreenContext, FullScreenContextProvider, useFullScreenContext};
Skalakid marked this conversation as resolved.
Show resolved Hide resolved
5 changes: 4 additions & 1 deletion src/components/VideoPlayerContexts/PlaybackContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ function PlaybackContextProvider({children}: ChildrenProps) {
const [originalParent, setOriginalParent] = useState<View | null>(null);
const currentVideoPlayerRef = useRef<Video | null>(null);
const {currentReportID} = useCurrentReportID() ?? {};
const videoResumeTryNumber = useRef(0);

const pauseVideo = useCallback(() => {
currentVideoPlayerRef.current?.setStatusAsync?.({shouldPlay: false});
Expand Down Expand Up @@ -69,6 +70,7 @@ function PlaybackContextProvider({children}: ChildrenProps) {
);

const resetVideoPlayerData = useCallback(() => {
videoResumeTryNumber.current = 0;
stopVideo();
setCurrentlyPlayingURL(null);
setSharedElement(null);
Expand All @@ -95,6 +97,7 @@ function PlaybackContextProvider({children}: ChildrenProps) {
playVideo,
pauseVideo,
checkVideoPlaying,
videoResumeTryNumber,
}),
[updateCurrentlyPlayingURL, currentlyPlayingURL, originalParent, sharedElement, shareVideoPlayerElements, playVideo, pauseVideo, checkVideoPlaying],
);
Expand All @@ -111,4 +114,4 @@ function usePlaybackContext() {

PlaybackContextProvider.displayName = 'PlaybackContextProvider';

export {PlaybackContextProvider, usePlaybackContext};
export {Context as PlaybackContext, PlaybackContextProvider, usePlaybackContext};
Loading
Loading