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

Ts migration videoplayercontext #70

Closed
wants to merge 11 commits into from
2 changes: 1 addition & 1 deletion ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1999,7 +1999,7 @@ SPEC CHECKSUMS:
SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17
Turf: 13d1a92d969ca0311bbc26e8356cca178ce95da2
VisionCamera: 0a6794d1974aed5d653d0d0cb900493e2583e35a
Yoga: e64aa65de36c0832d04e8c7bd614396c77a80047
Yoga: 13c8ef87792450193e117976337b8527b49e8c03

PODFILE CHECKSUM: 0ccbb4f2406893c6e9f266dc1e7470dcd72885d2

Expand Down
Original file line number Diff line number Diff line change
@@ -1,52 +1,42 @@
import PropTypes from 'prop-types';
import type {Video} from 'expo-av';
import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react';
import type {View} from 'react-native';
import useCurrentReportID from '@hooks/useCurrentReportID';
import type ChildrenProps from '@src/types/utils/ChildrenProps';
import type {PlaybackContext} from './types';

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

function PlaybackContextProvider({children}) {
const [currentlyPlayingURL, setCurrentlyPlayingURL] = useState(null);
const [sharedElement, setSharedElement] = useState(null);
const [originalParent, setOriginalParent] = useState(null);
const currentVideoPlayerRef = useRef(null);
const {currentReportID} = useCurrentReportID();
function PlaybackContextProvider({children}: ChildrenProps) {
const [currentlyPlayingURL, setCurrentlyPlayingURL] = useState<string | null>(null);
const [sharedElement, setSharedElement] = useState<View | null>(null);
const [originalParent, setOriginalParent] = useState<View | null>(null);
const currentVideoPlayerRef = useRef<Video | null>(null);
const {currentReportID} = useCurrentReportID() ?? {};

const pauseVideo = useCallback(() => {
if (!(currentVideoPlayerRef && currentVideoPlayerRef.current && currentVideoPlayerRef.current.setStatusAsync)) {
return;
}
currentVideoPlayerRef.current.setStatusAsync({shouldPlay: false});
currentVideoPlayerRef.current?.setStatusAsync({shouldPlay: false});
}, [currentVideoPlayerRef]);

const stopVideo = useCallback(() => {
if (!(currentVideoPlayerRef && currentVideoPlayerRef.current && currentVideoPlayerRef.current.stopAsync)) {
return;
}
currentVideoPlayerRef.current.stopAsync({shouldPlay: false});
currentVideoPlayerRef.current?.stopAsync?.();
}, [currentVideoPlayerRef]);

const playVideo = useCallback(() => {
if (!(currentVideoPlayerRef && currentVideoPlayerRef.current && currentVideoPlayerRef.current.setStatusAsync)) {
return;
}
currentVideoPlayerRef.current.getStatusAsync().then((status) => {
const newStatus = {shouldPlay: true};
if (status.durationMillis === status.positionMillis) {
newStatus.positionMillis = 0;
currentVideoPlayerRef.current?.getStatusAsync?.().then((status) => {
if ('durationMillis' in status && status.durationMillis === status.positionMillis) {
currentVideoPlayerRef.current?.setStatusAsync({shouldPlay: true, positionMillis: 0});
}
currentVideoPlayerRef.current.setStatusAsync(newStatus);
currentVideoPlayerRef.current?.setStatusAsync({shouldPlay: true});
});
}, [currentVideoPlayerRef]);

const unloadVideo = useCallback(() => {
if (!(currentVideoPlayerRef && currentVideoPlayerRef.current && currentVideoPlayerRef.current.unloadAsync)) {
return;
}
currentVideoPlayerRef.current.unloadAsync();
currentVideoPlayerRef.current?.unloadAsync?.();
}, [currentVideoPlayerRef]);

const updateCurrentlyPlayingURL = useCallback(
(url) => {
(url: string) => {
if (currentlyPlayingURL && url !== currentlyPlayingURL) {
pauseVideo();
}
Expand All @@ -56,7 +46,7 @@ function PlaybackContextProvider({children}) {
);

const shareVideoPlayerElements = useCallback(
(ref, parent, child) => {
(ref: Video, parent: View, child: View) => {
currentVideoPlayerRef.current = ref;
setOriginalParent(parent);
setSharedElement(child);
Expand All @@ -66,12 +56,9 @@ function PlaybackContextProvider({children}) {
);

const checkVideoPlaying = useCallback(
(statusCallback) => {
if (!(currentVideoPlayerRef && currentVideoPlayerRef.current && currentVideoPlayerRef.current.getStatusAsync)) {
return;
}
currentVideoPlayerRef.current.getStatusAsync().then((status) => {
statusCallback(status.isPlaying);
(statusCallback: (isPlaying: boolean) => void) => {
currentVideoPlayerRef.current?.getStatusAsync().then((status) => {
statusCallback('isPlaying' in status && status.isPlaying);
});
},
[currentVideoPlayerRef],
Expand Down Expand Up @@ -107,21 +94,17 @@ function PlaybackContextProvider({children}) {
}),
[updateCurrentlyPlayingURL, currentlyPlayingURL, originalParent, sharedElement, shareVideoPlayerElements, playVideo, pauseVideo, checkVideoPlaying],
);
return <PlaybackContext.Provider value={contextValue}>{children}</PlaybackContext.Provider>;
return <Context.Provider value={contextValue}>{children}</Context.Provider>;
}

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

PlaybackContextProvider.displayName = 'PlaybackContextProvider';
PlaybackContextProvider.propTypes = {
/** Actual content wrapped by this component */
children: PropTypes.node.isRequired,
};

export {PlaybackContextProvider, usePlaybackContext};
Original file line number Diff line number Diff line change
@@ -1,78 +1,73 @@
import PropTypes from 'prop-types';
import React, {useCallback, useContext, useMemo, useState} from 'react';
import _ from 'underscore';
import * as Expensicons from '@components/Icon/Expensicons';
import useLocalize from '@hooks/useLocalize';
import fileDownload from '@libs/fileDownload';
import * as Url from '@libs/Url';
import CONST from '@src/CONST';
import type ChildrenProps from '@src/types/utils/ChildrenProps';
import {usePlaybackContext} from './PlaybackContext';
import type {MenuItem, SingularMenuItem, VideoPopoverMenuContext} from './types';

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

function VideoPopoverMenuContextProvider({children}) {
function VideoPopoverMenuContextProvider({children}: ChildrenProps) {
const {currentVideoPlayerRef} = usePlaybackContext();
const {translate} = useLocalize();
const [currentPlaybackSpeed, setCurrentPlaybackSpeed] = useState(CONST.VIDEO_PLAYER.PLAYBACK_SPEEDS[2]);
const [currentPlaybackSpeed, setCurrentPlaybackSpeed] = useState<number>(CONST.VIDEO_PLAYER.PLAYBACK_SPEEDS[2]);

const updatePlaybackSpeed = useCallback(
(speed) => {
(speed: number) => {
setCurrentPlaybackSpeed(speed);
currentVideoPlayerRef.current.setStatusAsync({rate: speed});
currentVideoPlayerRef.current?.setStatusAsync({rate: speed});
},
[currentVideoPlayerRef],
);

const downloadAttachment = useCallback(() => {
currentVideoPlayerRef.current.getStatusAsync().then((status) => {
currentVideoPlayerRef.current?.getStatusAsync().then((status) => {
if (!('uri' in status)) {
return;
}
const sourceURI = `/${Url.getPathFromURL(status.uri)}`;
fileDownload(sourceURI);
});
}, [currentVideoPlayerRef]);

const menuItems = useMemo(
const menuItems = useMemo<[SingularMenuItem, MenuItem]>(
() => [
{
icon: Expensicons.Download,
text: translate('common.download'),
onSelected: () => {
downloadAttachment();
},
onSelected: downloadAttachment,
},
{
icon: Expensicons.Meter,
text: translate('videoPlayer.playbackSpeed'),
subMenuItems: [
..._.map(CONST.VIDEO_PLAYER.PLAYBACK_SPEEDS, (speed) => ({
icon: currentPlaybackSpeed === speed ? Expensicons.Checkmark : null,
text: speed.toString(),
onSelected: () => {
updatePlaybackSpeed(speed);
},
shouldPutLeftPaddingWhenNoIcon: true,
})),
],
subMenuItems: CONST.VIDEO_PLAYER.PLAYBACK_SPEEDS.map((speed) => ({
icon: currentPlaybackSpeed === speed ? Expensicons.Checkmark : null,
text: speed.toString(),
onSelected: () => {
updatePlaybackSpeed(speed);
},
shouldPutLeftPaddingWhenNoIcon: true,
})),
},
],
[currentPlaybackSpeed, downloadAttachment, translate, updatePlaybackSpeed],
);

const contextValue = useMemo(() => ({menuItems, updatePlaybackSpeed}), [menuItems, updatePlaybackSpeed]);
return <VideoPopoverMenuContext.Provider value={contextValue}>{children}</VideoPopoverMenuContext.Provider>;
return <Context.Provider value={contextValue}>{children}</Context.Provider>;
}

function useVideoPopoverMenuContext() {
const context = useContext(VideoPopoverMenuContext);
if (context === undefined) {
const videoPopooverMenuContext = useContext(Context);
if (!videoPopooverMenuContext) {
throw new Error('useVideoPopoverMenuContext must be used within a VideoPopoverMenuContext');
}
return context;
return videoPopooverMenuContext;
}

VideoPopoverMenuContextProvider.displayName = 'VideoPopoverMenuContextProvider';
VideoPopoverMenuContextProvider.propTypes = {
/** Actual content wrapped by this component */
children: PropTypes.node.isRequired,
};

export {VideoPopoverMenuContextProvider, useVideoPopoverMenuContext};
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import PropTypes from 'prop-types';
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 type {VolumeContext} from './types';

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

function VolumeContextProvider({children}) {
function VolumeContextProvider({children}: ChildrenProps) {
const {currentVideoPlayerRef, originalParent} = usePlaybackContext();
const volume = useSharedValue(0);

const updateVolume = useCallback(
(newVolume) => {
(newVolume: number) => {
if (!currentVideoPlayerRef.current) {
return;
}
Expand All @@ -30,21 +31,17 @@ function VolumeContextProvider({children}) {
}, [originalParent, updateVolume, volume.value]);

const contextValue = useMemo(() => ({updateVolume, volume}), [updateVolume, volume]);
return <VolumeContext.Provider value={contextValue}>{children}</VolumeContext.Provider>;
return <Context.Provider value={contextValue}>{children}</Context.Provider>;
}

function useVolumeContext() {
const context = useContext(VolumeContext);
if (context === undefined) {
const volumeContext = useContext(Context);
if (!volumeContext) {
throw new Error('useVolumeContext must be used within a VolumeContextProvider');
}
return context;
return volumeContext;
}

VolumeContextProvider.displayName = 'VolumeContextProvider';
VolumeContextProvider.propTypes = {
/** Actual content wrapped by this component */
children: PropTypes.node.isRequired,
};

export {VolumeContextProvider, useVolumeContext};
42 changes: 42 additions & 0 deletions src/components/VideoPlayerContexts/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type {Video} from 'expo-av';
import type {MutableRefObject} from 'react';
import type {View} from 'react-native';
import type {SharedValue} from 'react-native-reanimated';
import type IconAsset from '@src/types/utils/IconAsset';

type PlaybackContext = {
updateCurrentlyPlayingURL: (url: string) => void;
currentlyPlayingURL: string | null;
originalParent: View | null;
sharedElement: View | null;
currentVideoPlayerRef: MutableRefObject<Video | null>;
shareVideoPlayerElements: (ref: Video, parent: View, child: View) => void;
playVideo: () => void;
pauseVideo: () => void;
checkVideoPlaying: (statusCallback: (isPlaying: boolean) => void) => void;
};

type VolumeContext = {
updateVolume: (newVolume: number) => void;
volume: SharedValue<number>;
};

type SingularMenuItem = {
icon: IconAsset | null;
text: string;
onSelected: () => void;
shouldPutLeftPaddingWhenNoIcon?: boolean;
};

type MenuItem = {
icon: IconAsset;
text: string;
subMenuItems: SingularMenuItem[];
};

type VideoPopoverMenuContext = {
menuItems: [SingularMenuItem, MenuItem];
updatePlaybackSpeed: (speed: number) => void;
};

export type {PlaybackContext, VolumeContext, VideoPopoverMenuContext, MenuItem, SingularMenuItem};
1 change: 1 addition & 0 deletions src/libs/fileDownload/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const fileDownload: FileDownload = (url, fileName) => {
// adding href to anchor
link.href = href;
link.style.display = 'none';
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- nullish coalescing doesn't achieve the same result in this case
link.download = FileUtils.appendTimeToFileName(fileName || FileUtils.getFileName(url));

// Append to html link element page
Expand Down
2 changes: 1 addition & 1 deletion src/libs/fileDownload/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type {Asset} from 'react-native-image-picker';

type FileDownload = (url: string, fileName: string, successMessage?: string) => Promise<void>;
type FileDownload = (url: string, fileName?: string, successMessage?: string) => Promise<void>;

type ImageResolution = {width: number; height: number};
type GetImageResolution = (url: File | Asset) => Promise<ImageResolution>;
Expand Down
Loading