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

[Videos] Add error boundary to native #4914

Merged
merged 2 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
71 changes: 48 additions & 23 deletions src/view/com/util/post-embeds/VideoEmbed.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
import React from 'react'
import React, {useCallback, useState} from 'react'
import {View} from 'react-native'
import {msg} from '@lingui/macro'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'

import {VideoEmbedInnerNative} from 'view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative'
import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonIcon} from '#/components/Button'
import {Play_Filled_Corner2_Rounded as PlayIcon} from '#/components/icons/Play'
import {VisibilityView} from '../../../../../modules/expo-bluesky-swiss-army'
import {ErrorBoundary} from '../ErrorBoundary'
import {useActiveVideoView} from './ActiveVideoContext'
import * as VideoFallback from './VideoEmbedInner/VideoFallback'

export function VideoEmbed({source}: {source: string}) {
const t = useTheme()
const {active, setActive} = useActiveVideoView({source})
const {_} = useLingui()

const [key, setKey] = useState(0)
const renderError = useCallback(
(error: unknown) => (
<VideoError error={error} retry={() => setKey(key + 1)} />
),
[key],
)

return (
<View
style={[
Expand All @@ -25,27 +35,42 @@ export function VideoEmbed({source}: {source: string}) {
t.atoms.bg_contrast_25,
a.my_xs,
]}>
<VisibilityView
enabled={true}
onChangeStatus={isActive => {
if (isActive) {
setActive()
}
}}>
{active ? (
<VideoEmbedInnerNative />
) : (
<Button
style={[a.flex_1, t.atoms.bg_contrast_25]}
onPress={setActive}
label={_(msg`Play video`)}
variant="ghost"
color="secondary"
size="large">
<ButtonIcon icon={PlayIcon} />
</Button>
)}
</VisibilityView>
<ErrorBoundary renderError={renderError} key={key}>
<VisibilityView
enabled={true}
onChangeStatus={isActive => {
if (isActive) {
setActive()
}
}}>
{active ? (
<VideoEmbedInnerNative />
) : (
<Button
style={[a.flex_1, t.atoms.bg_contrast_25]}
onPress={setActive}
label={_(msg`Play video`)}
variant="ghost"
color="secondary"
size="large">
<ButtonIcon icon={PlayIcon} />
</Button>
)}
</VisibilityView>
</ErrorBoundary>
</View>
)
}

function VideoError({retry}: {error: unknown; retry: () => void}) {
return (
<VideoFallback.Container>
<VideoFallback.Text>
<Trans>
An error occurred while loading the video. Please try again later.
</Trans>
</VideoFallback.Text>
<VideoFallback.RetryButton onPress={retry} />
</VideoFallback.Container>
)
}
48 changes: 7 additions & 41 deletions src/view/com/util/post-embeds/VideoEmbed.web.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import React, {useCallback, useEffect, useRef, useState} from 'react'
import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/macro'

import {
HLSUnsupportedError,
VideoEmbedInnerWeb,
} from 'view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb'
import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonText} from '#/components/Button'
import {Text} from '#/components/Typography'
import {ErrorBoundary} from '../ErrorBoundary'
import {useActiveVideoView} from './ActiveVideoContext'
import * as VideoFallback from './VideoEmbedInner/VideoFallback'

export function VideoEmbed({source}: {source: string}) {
const t = useTheme()
Expand Down Expand Up @@ -138,32 +136,11 @@ function ViewportObserver({
}

function VideoError({error, retry}: {error: unknown; retry: () => void}) {
const t = useTheme()
const {_} = useLingui()

const isHLS = error instanceof HLSUnsupportedError

return (
<View
style={[
a.flex_1,
t.atoms.bg_contrast_25,
a.justify_center,
a.align_center,
a.px_lg,
a.border,
t.atoms.border_contrast_low,
a.rounded_sm,
a.gap_lg,
]}>
<Text
style={[
a.text_center,
t.atoms.text_contrast_high,
a.text_md,
a.leading_snug,
{maxWidth: 300},
]}>
<VideoFallback.Container>
<VideoFallback.Text>
{isHLS ? (
<Trans>
Your browser does not support the video format. Please try a
Expand All @@ -174,19 +151,8 @@ function VideoError({error, retry}: {error: unknown; retry: () => void}) {
An error occurred while loading the video. Please try again later.
</Trans>
)}
</Text>
{!isHLS && (
<Button
onPress={retry}
size="small"
color="secondary_inverted"
variant="solid"
label={_(msg`Retry`)}>
<ButtonText>
<Trans>Retry</Trans>
</ButtonText>
</Button>
)}
</View>
</VideoFallback.Text>
{!isHLS && <VideoFallback.RetryButton onPress={retry} />}
</VideoFallback.Container>
)
}
61 changes: 61 additions & 0 deletions src/view/com/util/post-embeds/VideoEmbedInner/VideoFallback.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from 'react'
import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'

import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonText} from '#/components/Button'
import {Text as TypoText} from '#/components/Typography'

export function Container({children}: {children: React.ReactNode}) {
const t = useTheme()
return (
<View
style={[
a.flex_1,
t.atoms.bg_contrast_25,
a.justify_center,
a.align_center,
a.px_lg,
a.border,
t.atoms.border_contrast_low,
a.rounded_sm,
a.gap_lg,
]}>
{children}
</View>
)
}

export function Text({children}: {children: React.ReactNode}) {
const t = useTheme()
return (
<TypoText
style={[
a.text_center,
t.atoms.text_contrast_high,
a.text_md,
a.leading_snug,
{maxWidth: 300},
]}>
{children}
</TypoText>
)
}

export function RetryButton({onPress}: {onPress: () => void}) {
const {_} = useLingui()

return (
<Button
onPress={onPress}
size="small"
color="secondary_inverted"
variant="solid"
label={_(msg`Retry`)}>
<ButtonText>
<Trans>Retry</Trans>
</ButtonText>
</Button>
)
}
Loading