206 lines
7.3 KiB
TypeScript
206 lines
7.3 KiB
TypeScript
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react';
|
|
import {DeviceEventEmitter, StyleSheet} from 'react-native';
|
|
import Animated, {
|
|
Easing,
|
|
useAnimatedStyle,
|
|
useSharedValue,
|
|
withTiming,
|
|
type WithTimingConfig,
|
|
} from 'react-native-reanimated';
|
|
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
|
import Video, {SelectedTrackType, type OnPlaybackRateChangeData, type ReactVideoPoster, type ReactVideoSource, type VideoRef} from 'react-native-video';
|
|
|
|
import {updateLocalFilePath} from '@actions/local/file';
|
|
import {CaptionsEnabledContext} from '@calls/context';
|
|
import {getTranscriptionUri} from '@calls/utils';
|
|
import {Events} from '@constants';
|
|
import {GALLERY_FOOTER_HEIGHT, VIDEO_INSET} from '@constants/gallery';
|
|
import {useServerUrl} from '@context/server';
|
|
|
|
import DownloadWithAction from '../footer/download_with_action';
|
|
|
|
import VideoError from './error';
|
|
|
|
import type {ImageRendererProps} from '../image_renderer';
|
|
import type {GalleryAction} from '@typings/screens/gallery';
|
|
|
|
export interface VideoRendererProps extends ImageRendererProps {
|
|
canDownloadFiles: boolean;
|
|
enableSecureFilePreview: boolean;
|
|
index: number;
|
|
initialIndex: number;
|
|
onShouldHideControls: (hide: boolean) => void;
|
|
}
|
|
|
|
const timingConfig: WithTimingConfig = {
|
|
duration: 250,
|
|
easing: Easing.bezier(0.33, 0.01, 0, 1),
|
|
};
|
|
|
|
const styles = StyleSheet.create({
|
|
video: {
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
position: 'absolute',
|
|
},
|
|
});
|
|
|
|
const VideoRenderer = ({canDownloadFiles, enableSecureFilePreview, height, index, initialIndex, item, isPageActive, onShouldHideControls, width}: VideoRendererProps) => {
|
|
const fullscreen = useSharedValue(false);
|
|
const {bottom} = useSafeAreaInsets();
|
|
const serverUrl = useServerUrl();
|
|
const videoRef = useRef<VideoRef>();
|
|
const showControls = useRef(!(initialIndex === index));
|
|
const captionsEnabled = useContext(CaptionsEnabledContext);
|
|
const [paused, setPaused] = useState(!(initialIndex === index));
|
|
const [videoReady, setVideoReady] = useState(false);
|
|
const [videoUri, setVideoUri] = useState(item.uri);
|
|
const [downloading, setDownloading] = useState(false);
|
|
const [hasError, setHasError] = useState(false);
|
|
const {tracks, selected} = useMemo(() => getTranscriptionUri(serverUrl, item.postProps), [serverUrl, item.postProps]);
|
|
const source: ReactVideoSource = useMemo(() => ({uri: videoUri, textTracks: tracks}), [videoUri, tracks]);
|
|
const poster: ReactVideoPoster = useMemo(() => ({
|
|
source: {uri: item.posterUri},
|
|
resizeMode: 'center',
|
|
}), [item.posterUri]);
|
|
|
|
const setFullscreen = useCallback((value: boolean) => {
|
|
fullscreen.value = value;
|
|
}, []);
|
|
|
|
const onDownloadSuccess = (path: string) => {
|
|
setVideoUri(path);
|
|
setHasError(false);
|
|
updateLocalFilePath(serverUrl, item.id, path);
|
|
};
|
|
|
|
const onEnd = useCallback(() => {
|
|
setFullscreen(false);
|
|
onShouldHideControls(true);
|
|
showControls.current = true;
|
|
setPaused(true);
|
|
videoRef.current?.dismissFullscreenPlayer();
|
|
}, [onShouldHideControls, setFullscreen]);
|
|
|
|
const onError = useCallback(() => {
|
|
setHasError(true);
|
|
}, []);
|
|
|
|
const onFullscreenPlayerWillDismiss = useCallback(() => {
|
|
setFullscreen(false);
|
|
showControls.current = !paused;
|
|
onShouldHideControls(showControls.current);
|
|
}, [setFullscreen, paused, onShouldHideControls]);
|
|
|
|
const onFullscreenPlayerWillPresent = useCallback(() => {
|
|
setFullscreen(true);
|
|
onShouldHideControls(true);
|
|
showControls.current = true;
|
|
}, [onShouldHideControls, setFullscreen]);
|
|
|
|
const onPlaybackRateChange = useCallback(({playbackRate}: OnPlaybackRateChangeData) => {
|
|
if (isPageActive.value) {
|
|
const isPlaying = Boolean(playbackRate);
|
|
showControls.current = isPlaying;
|
|
onShouldHideControls(isPlaying);
|
|
}
|
|
}, [isPageActive.value, onShouldHideControls]);
|
|
|
|
const onReadyForDisplay = useCallback(() => {
|
|
setVideoReady(true);
|
|
setHasError(false);
|
|
}, []);
|
|
|
|
const handleTouchStart = useCallback(() => {
|
|
showControls.current = !showControls.current;
|
|
onShouldHideControls(showControls.current);
|
|
}, [onShouldHideControls]);
|
|
|
|
const setGalleryAction = useCallback((action: GalleryAction) => {
|
|
DeviceEventEmitter.emit(Events.GALLERY_ACTIONS, action);
|
|
if (action === 'none') {
|
|
setDownloading(false);
|
|
}
|
|
}, []);
|
|
|
|
const dimensionsStyle = useMemo(() => {
|
|
const w = width;
|
|
const h = height - (VIDEO_INSET + GALLERY_FOOTER_HEIGHT + bottom);
|
|
|
|
return {width: w, height: h};
|
|
}, [width, height, bottom]);
|
|
|
|
const animatedStyle = useAnimatedStyle(() => {
|
|
return {
|
|
width: withTiming(dimensionsStyle.width, timingConfig),
|
|
height: withTiming(dimensionsStyle.height, timingConfig),
|
|
};
|
|
}, [dimensionsStyle]);
|
|
|
|
useEffect(() => {
|
|
if (initialIndex === index && videoReady) {
|
|
setPaused(false);
|
|
} else if (videoReady) {
|
|
videoRef.current?.seek(0.4);
|
|
}
|
|
}, [index, initialIndex, videoReady]);
|
|
|
|
useEffect(() => {
|
|
if (!isPageActive.value && !paused) {
|
|
setPaused(true);
|
|
videoRef.current?.dismissFullscreenPlayer();
|
|
}
|
|
}, [isPageActive.value, paused]);
|
|
|
|
return (
|
|
<Animated.View style={animatedStyle}>
|
|
<Video
|
|
|
|
//@ts-expect-error legacy ref
|
|
ref={videoRef}
|
|
source={source}
|
|
paused={paused}
|
|
poster={poster}
|
|
onError={onError}
|
|
style={[styles.video, dimensionsStyle]}
|
|
controls={isPageActive.value}
|
|
onPlaybackRateChange={onPlaybackRateChange}
|
|
onFullscreenPlayerWillDismiss={onFullscreenPlayerWillDismiss}
|
|
onFullscreenPlayerWillPresent={onFullscreenPlayerWillPresent}
|
|
onReadyForDisplay={onReadyForDisplay}
|
|
onEnd={onEnd}
|
|
onTouchStart={handleTouchStart}
|
|
resizeMode='none'
|
|
selectedTextTrack={captionsEnabled[index] ? selected : {type: SelectedTrackType.DISABLED, value: ''}}
|
|
/>
|
|
{hasError &&
|
|
<VideoError
|
|
canDownloadFiles={canDownloadFiles}
|
|
enableSecureFilePreview={enableSecureFilePreview}
|
|
filename={item.name}
|
|
isDownloading={downloading}
|
|
isRemote={videoUri.startsWith('http')}
|
|
onShouldHideControls={handleTouchStart}
|
|
posterUri={item.posterUri}
|
|
setDownloading={setDownloading}
|
|
height={item.height}
|
|
width={item.width}
|
|
/>
|
|
}
|
|
{downloading &&
|
|
<DownloadWithAction
|
|
action='external'
|
|
enableSecureFilePreview={enableSecureFilePreview}
|
|
setAction={setGalleryAction}
|
|
onDownloadSuccess={onDownloadSuccess}
|
|
item={item}
|
|
/>
|
|
}
|
|
</Animated.View>
|
|
);
|
|
};
|
|
|
|
export default VideoRenderer;
|