Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
d34415b
added video slash svg
HezekielT Feb 24, 2025
cc1484d
style change in AttachmentOfflineIndicator
HezekielT Feb 24, 2025
bb72f96
include video slash in Expensicons
HezekielT Feb 24, 2025
e9dbcd9
create a new component VideoErrorIndicator
HezekielT Feb 24, 2025
33b4f6a
update in PlaybackContext
HezekielT Feb 24, 2025
3369bba
handle errors generated by unsupported video inside BaseVideoPlayer
HezekielT Feb 24, 2025
7aab44a
created a patch for expo-av that handles unsupported video for ios
HezekielT Feb 24, 2025
3fb9c57
run prettier
HezekielT Feb 24, 2025
95a183c
remove incorrect asset
HezekielT Feb 24, 2025
a16bf25
Merge branch 'Expensify:main' into fix/ios-handle-unsupported-video-f…
HezekielT Feb 24, 2025
038cde0
added video-slash to assets/images
HezekielT Feb 24, 2025
494b566
Merge branch 'Expensify:main' into fix/ios-handle-unsupported-video-f…
HezekielT Feb 24, 2025
7618a3b
run prettier
HezekielT Feb 25, 2025
425d489
fix eslint error
HezekielT Feb 25, 2025
ec82aac
remove unnecessary style changes
HezekielT Feb 27, 2025
5db9b82
Add an error occurred message to en.ts and es.ts
HezekielT Mar 1, 2025
af9ba7e
added videoErrorText style
HezekielT Mar 1, 2025
d596b83
show text and icon color based on the value of isPreview passed to Vi…
HezekielT Mar 2, 2025
d591aeb
passed isPreview to VideoErrorIndicator
HezekielT Mar 2, 2025
daf4a43
run prettier
HezekielT Mar 2, 2025
39aadd4
Merge branch 'main' of https://github.com/HezekielT/App into fix/ios-…
HezekielT Mar 2, 2025
b9a56b3
added isBuffering check to VideoErrorIndicator
HezekielT Mar 4, 2025
cf4f417
Merge branch 'main' of https://github.com/HezekielT/App into fix/ios-…
HezekielT Mar 4, 2025
de2d31d
remove unnecessary view wrapper
HezekielT Mar 6, 2025
5402bed
update style
HezekielT Mar 6, 2025
d1c8d68
remove unnecessary style
HezekielT Mar 6, 2025
4f66006
added a horizontal padding of 44 to spacing.ts
HezekielT Mar 6, 2025
765e9db
Merge branch 'main' of https://github.com/HezekielT/App into fix/ios-…
HezekielT Mar 7, 2025
8c1cf87
fixed styling issue in attachment offline indicator
HezekielT Mar 7, 2025
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
14 changes: 14 additions & 0 deletions assets/images/video-slash.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 22 additions & 0 deletions patches/expo-av+15.0.1.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
diff --git a/node_modules/expo-av/ios/EXAV/EXAVPlayerData.m b/node_modules/expo-av/ios/EXAV/EXAVPlayerData.m
index 99dc808..01e4bb9 100644
--- a/node_modules/expo-av/ios/EXAV/EXAVPlayerData.m
+++ b/node_modules/expo-av/ios/EXAV/EXAVPlayerData.m
@@ -158,8 +158,16 @@ NSString *const EXAVPlayerDataObserverMetadataKeyPath = @"timedMetadata";
// unless we preload, the asset will not necessarily load the duration by the time we try to play it.
// http://stackoverflow.com/questions/20581567/avplayer-and-avfoundationerrordomain-code-11819
EX_WEAKIFY(self);
- [avAsset loadValuesAsynchronouslyForKeys:@[ @"duration" ] completionHandler:^{
+ [avAsset loadValuesAsynchronouslyForKeys:@[ @"isPlayable", @"duration" ] completionHandler:^{
EX_ENSURE_STRONGIFY(self);
+ NSError *error = nil;
+ AVKeyValueStatus status = [avAsset statusOfValueForKey:@"isPlayable" error:&error];
+
+ if (status == AVKeyValueStatusLoaded && !avAsset.isPlayable) {
+ NSString *errorMessage = @"Load encountered an error: [AVAsset isPlayable:] returned false. The asset does not contains a playable content or is not supported by the device.";
+ [self _finishLoadWithError:errorMessage];
+ return;
+ }

// We prepare three items for AVQueuePlayer, so when the first finishes playing,
// second can start playing and the third can start preparing to play.
4 changes: 2 additions & 2 deletions src/components/AttachmentOfflineIndicator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ function AttachmentOfflineIndicator({isPreview = false}: AttachmentOfflineIndica
/>
{!isPreview && (
<View>
<Text style={[styles.notFoundTextHeader]}>{translate('common.youAppearToBeOffline')}</Text>
<Text>{translate('common.attachementWillBeAvailableOnceBackOnline')}</Text>
<Text style={[styles.notFoundTextHeader, styles.ph10]}>{translate('common.youAppearToBeOffline')}</Text>
<Text style={[styles.textAlignCenter, styles.ph11, styles.textSupporting]}>{translate('common.attachementWillBeAvailableOnceBackOnline')}</Text>
</View>
)}
</View>
Expand Down
2 changes: 2 additions & 0 deletions src/components/Icon/Expensicons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ import UserEye from '@assets/images/user-eye.svg';
import UserPlus from '@assets/images/user-plus.svg';
import User from '@assets/images/user.svg';
import Users from '@assets/images/users.svg';
import VideoSlash from '@assets/images/video-slash.svg';
import VolumeHigh from '@assets/images/volume-high.svg';
import VolumeLow from '@assets/images/volume-low.svg';
import Wallet from '@assets/images/wallet.svg';
Expand Down Expand Up @@ -383,6 +384,7 @@ export {
User,
UserCheck,
Users,
VideoSlash,
VolumeHigh,
VolumeLow,
Wallet,
Expand Down
10 changes: 9 additions & 1 deletion src/components/VideoPlayer/BaseVideoPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import shouldReplayVideo from './shouldReplayVideo';
import type {VideoPlayerProps, VideoWithOnFullScreenUpdate} from './types';
import useHandleNativeVideoControls from './useHandleNativeVideoControls';
import * as VideoUtils from './utils';
import VideoErrorIndicator from './VideoErrorIndicator';
import VideoPlayerControls from './VideoPlayerControls';

function BaseVideoPlayer({
Expand Down Expand Up @@ -75,6 +76,7 @@ function BaseVideoPlayer({
const [isLoading, setIsLoading] = useState(true);
const [isEnded, setIsEnded] = useState(false);
const [isBuffering, setIsBuffering] = useState(true);
const [hasError, setHasError] = useState(false);
// we add "#t=0.001" at the end of the URL to skip first milisecond of the video and always be able to show proper video preview when video is paused at the beginning
const [sourceURL] = useState(() => VideoUtils.addSkipTimeTagToURL(url.includes('blob:') || url.includes('file:///') ? url : addEncryptedAuthTokenToURL(url), 0.001));
const [isPopoverVisible, setIsPopoverVisible] = useState(false);
Expand Down Expand Up @@ -489,11 +491,17 @@ function BaseVideoPlayer({
}}
onPlaybackStatusUpdate={handlePlaybackStatusUpdate}
onFullscreenUpdate={handleFullscreenUpdate}
onError={() => {
setHasError(true);
}}
/>
</View>
)}
</PressableWithoutFeedback>
{((isLoading && !isOffline) || (isBuffering && !isPlaying)) && <FullScreenLoadingIndicator style={[styles.opacity1, styles.bgTransparent]} />}
{hasError && !isBuffering && !isOffline && <VideoErrorIndicator isPreview={isPreview} />}
{((isLoading && !isOffline && !hasError) || (isBuffering && !isPlaying && !hasError)) && (
<FullScreenLoadingIndicator style={[styles.opacity1, styles.bgTransparent]} />
)}
{isLoading && (isOffline || !isBuffering) && <AttachmentOfflineIndicator isPreview={isPreview} />}
{controlStatusState !== CONST.VIDEO_PLAYER.CONTROLS_STATUS.HIDE && !isLoading && (isPopoverVisible || isHovered || canUseTouchScreen || isEnded) && (
<VideoPlayerControls
Expand Down
40 changes: 40 additions & 0 deletions src/components/VideoPlayer/VideoErrorIndicator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react';
import {View} from 'react-native';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import variables from '@styles/variables';

type VideoErrorIndicatorProps = {
/** Whether it is a preview or not */
isPreview?: boolean;
};

function VideoErrorIndicator({isPreview = false}: VideoErrorIndicatorProps) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
function VideoErrorIndicator({isPreview = false}: VideoErrorIndicatorProps) {
function VideoErrorIndicator({shouldShowErrorText = false}: VideoErrorIndicatorProps) {

I think shouldShowErrorText is more generic. What do you think?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we can go with shouldShowErrorText but isPreview is also used in VideoErrorIndicator in order to determine the color of video slash icon when it is in preview and in attachment modal so if we replace it with shouldShowErrorText it might become confusing.

<Icon
                fill={isPreview ? theme.border : theme.icon}

Also AttachmentOfflineIndicator uses isPreview as the name while showing text.

Based on this, do you think we should keep using it as isPreview or should we still update it shouldShowErrorText

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer shouldShowErrorText but seems we need to keep isPreview to keep consistency with AttachmentOfflineIndicator

const theme = useTheme();
const styles = useThemeStyles();
const {translate} = useLocalize();

return (
<View style={[styles.flexColumn, styles.alignItemsCenter, styles.justifyContentCenter, styles.pAbsolute, styles.h100, styles.w100]}>
<Icon
fill={isPreview ? theme.border : theme.icon}
src={Expensicons.VideoSlash}
width={variables.eReceiptEmptyIconWidth}
height={variables.eReceiptEmptyIconWidth}
/>
{!isPreview && (
<View>
<Text style={[styles.notFoundTextHeader, styles.ph11]}>{translate('common.errorOccuredWhileTryingToPlayVideo')}</Text>
</View>
)}
</View>
);
}

VideoErrorIndicator.displayName = 'VideoErrorIndicator';

export default VideoErrorIndicator;
4 changes: 3 additions & 1 deletion src/components/VideoPlayerContexts/PlaybackContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ function PlaybackContextProvider({children}: ChildrenProps) {
if ('durationMillis' in status && status.durationMillis === status.positionMillis) {
newStatus.positionMillis = 0;
}
playVideoPromiseRef.current = currentVideoPlayerRef.current?.setStatusAsync(newStatus);
playVideoPromiseRef.current = currentVideoPlayerRef.current?.setStatusAsync(newStatus).catch((error: AVPlaybackStatus) => {
return error;
});
});
}, [currentVideoPlayerRef]);

Expand Down
1 change: 1 addition & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,7 @@ const translations = {
youAppearToBeOffline: 'You appear to be offline.',
thisFeatureRequiresInternet: 'This feature requires an active internet connection.',
attachementWillBeAvailableOnceBackOnline: 'Attachment will become available once back online.',
errorOccuredWhileTryingToPlayVideo: 'An error occurred while trying to play this video.',
areYouSure: 'Are you sure?',
verify: 'Verify',
yesContinue: 'Yes, continue',
Expand Down
1 change: 1 addition & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@ const translations = {
youAppearToBeOffline: 'Parece que estás desconectado.',
thisFeatureRequiresInternet: 'Esta función requiere una conexión a Internet activa.',
attachementWillBeAvailableOnceBackOnline: 'El archivo adjunto estará disponible cuando vuelvas a estar en línea.',
errorOccuredWhileTryingToPlayVideo: 'Se produjo un error al intentar reproducir este video.',
areYouSure: '¿Estás seguro?',
verify: 'Verifique',
yesContinue: 'Sí, continuar',
Expand Down
4 changes: 4 additions & 0 deletions src/styles/utils/spacing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,10 @@ export default {
paddingHorizontal: 40,
},

ph11: {
paddingHorizontal: 44,
},

ph15: {
paddingHorizontal: 60,
},
Expand Down