about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/view/com/util/post-embeds/VideoEmbed.tsx12
-rw-r--r--src/view/com/util/post-embeds/VideoEmbed.web.tsx35
-rw-r--r--src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb.tsx27
3 files changed, 55 insertions, 19 deletions
diff --git a/src/view/com/util/post-embeds/VideoEmbed.tsx b/src/view/com/util/post-embeds/VideoEmbed.tsx
index d10a6fe69..2dafce7ba 100644
--- a/src/view/com/util/post-embeds/VideoEmbed.tsx
+++ b/src/view/com/util/post-embeds/VideoEmbed.tsx
@@ -1,7 +1,7 @@
 import React, {useCallback, useEffect, useId, useState} from 'react'
 import {View} from 'react-native'
 import {Image} from 'expo-image'
-import {VideoPlayerStatus} from 'expo-video'
+import {PlayerError, VideoPlayerStatus} from 'expo-video'
 import {AppBskyEmbedVideo} from '@atproto/api'
 import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
@@ -78,6 +78,12 @@ function InnerWrapper({embed}: Props) {
     (playerStatus === 'waitingToPlayAtSpecifiedRate' ||
       playerStatus === 'loading')
 
+  // send error up to error boundary
+  const [error, setError] = useState<Error | PlayerError | null>(null)
+  if (error) {
+    throw error
+  }
+
   useEffect(() => {
     if (isActive) {
       // eslint-disable-next-line @typescript-eslint/no-shadow
@@ -92,10 +98,10 @@ function InnerWrapper({embed}: Props) {
       )
       const statusSub = player.addListener(
         'statusChange',
-        (status, _oldStatus, error) => {
+        (status, _oldStatus, playerError) => {
           setPlayerStatus(status)
           if (status === 'error') {
-            throw error
+            setError(playerError ?? new Error('Unknown player error'))
           }
         },
       )
diff --git a/src/view/com/util/post-embeds/VideoEmbed.web.tsx b/src/view/com/util/post-embeds/VideoEmbed.web.tsx
index 0001a7af5..a25f94641 100644
--- a/src/view/com/util/post-embeds/VideoEmbed.web.tsx
+++ b/src/view/com/util/post-embeds/VideoEmbed.web.tsx
@@ -1,13 +1,15 @@
 import React, {useCallback, useEffect, useRef, useState} from 'react'
 import {View} from 'react-native'
 import {AppBskyEmbedVideo} from '@atproto/api'
-import {Trans} from '@lingui/macro'
+import {msg} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
 
 import {clamp} from '#/lib/numbers'
 import {useGate} from '#/lib/statsig/statsig'
 import {
   HLSUnsupportedError,
   VideoEmbedInnerWeb,
+  VideoNotFoundError,
 } from '#/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb'
 import {atoms as a} from '#/alf'
 import {ErrorBoundary} from '../ErrorBoundary'
@@ -152,23 +154,26 @@ function ViewportObserver({
 }
 
 function VideoError({error, retry}: {error: unknown; retry: () => void}) {
-  const isHLS = error instanceof HLSUnsupportedError
+  const {_} = useLingui()
+
+  let showRetryButton = true
+  let text = null
+
+  if (error instanceof VideoNotFoundError) {
+    text = _(msg`Video not found.`)
+  } else if (error instanceof HLSUnsupportedError) {
+    showRetryButton = false
+    text = _(
+      msg`Your browser does not support the video format. Please try a different browser.`,
+    )
+  } else {
+    text = _(msg`An error occurred while loading the video. Please try again.`)
+  }
 
   return (
     <VideoFallback.Container>
-      <VideoFallback.Text>
-        {isHLS ? (
-          <Trans>
-            Your browser does not support the video format. Please try a
-            different browser.
-          </Trans>
-        ) : (
-          <Trans>
-            An error occurred while loading the video. Please try again later.
-          </Trans>
-        )}
-      </VideoFallback.Text>
-      {!isHLS && <VideoFallback.RetryButton onPress={retry} />}
+      <VideoFallback.Text>{text}</VideoFallback.Text>
+      {showRetryButton && <VideoFallback.RetryButton onPress={retry} />}
     </VideoFallback.Container>
   )
 }
diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb.tsx
index 77295c00c..a30c0e1e9 100644
--- a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb.tsx
+++ b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb.tsx
@@ -23,6 +23,12 @@ export function VideoEmbedInnerWeb({
   const [hasSubtitleTrack, setHasSubtitleTrack] = useState(false)
   const figId = useId()
 
+  // send error up to error boundary
+  const [error, setError] = useState<Error | null>(null)
+  if (error) {
+    throw error
+  }
+
   const hlsRef = useRef<Hls | undefined>(undefined)
 
   useEffect(() => {
@@ -38,12 +44,25 @@ export function VideoEmbedInnerWeb({
     // initial value, later on it's managed by Controls
     hls.autoLevelCapping = 0
 
-    hls.on(Hls.Events.SUBTITLE_TRACKS_UPDATED, (event, data) => {
+    hls.on(Hls.Events.SUBTITLE_TRACKS_UPDATED, (_event, data) => {
       if (data.subtitleTracks.length > 0) {
         setHasSubtitleTrack(true)
       }
     })
 
+    hls.on(Hls.Events.ERROR, (_event, data) => {
+      if (data.fatal) {
+        if (
+          data.details === 'manifestLoadError' &&
+          data.response?.code === 404
+        ) {
+          setError(new VideoNotFoundError())
+        } else {
+          setError(data.error)
+        }
+      }
+    })
+
     return () => {
       hlsRef.current = undefined
       hls.detachMedia()
@@ -104,3 +123,9 @@ export class HLSUnsupportedError extends Error {
     super('HLS is not supported')
   }
 }
+
+export class VideoNotFoundError extends Error {
+  constructor() {
+    super('Video not found')
+  }
+}