about summary refs log tree commit diff
path: root/src/view/com/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com/util')
-rw-r--r--src/view/com/util/Views.web.tsx8
-rw-r--r--src/view/com/util/images/Gallery.tsx2
-rw-r--r--src/view/com/util/images/ImageLayoutGrid.tsx2
-rw-r--r--src/view/com/util/post-embeds/ActiveVideoWebContext.tsx114
-rw-r--r--src/view/com/util/post-embeds/ExternalGifEmbed.tsx147
-rw-r--r--src/view/com/util/post-embeds/ExternalLinkEmbed.tsx182
-rw-r--r--src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx281
-rw-r--r--src/view/com/util/post-embeds/GifEmbed.tsx224
-rw-r--r--src/view/com/util/post-embeds/QuoteEmbed.tsx337
-rw-r--r--src/view/com/util/post-embeds/VideoEmbed.tsx167
-rw-r--r--src/view/com/util/post-embeds/VideoEmbed.web.tsx207
-rw-r--r--src/view/com/util/post-embeds/VideoEmbedInner/TimeIndicator.tsx64
-rw-r--r--src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx210
-rw-r--r--src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.web.tsx3
-rw-r--r--src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb.native.tsx3
-rw-r--r--src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb.tsx307
-rw-r--r--src/view/com/util/post-embeds/VideoEmbedInner/VideoFallback.tsx61
-rw-r--r--src/view/com/util/post-embeds/VideoEmbedInner/bandwidth-estimate.ts11
-rw-r--r--src/view/com/util/post-embeds/VideoEmbedInner/web-controls/ControlButton.tsx42
-rw-r--r--src/view/com/util/post-embeds/VideoEmbedInner/web-controls/Scrubber.tsx238
-rw-r--r--src/view/com/util/post-embeds/VideoEmbedInner/web-controls/VideoControls.native.tsx3
-rw-r--r--src/view/com/util/post-embeds/VideoEmbedInner/web-controls/VideoControls.tsx427
-rw-r--r--src/view/com/util/post-embeds/VideoEmbedInner/web-controls/VolumeControl.tsx110
-rw-r--r--src/view/com/util/post-embeds/VideoEmbedInner/web-controls/utils.tsx240
-rw-r--r--src/view/com/util/post-embeds/VideoVolumeContext.tsx47
-rw-r--r--src/view/com/util/post-embeds/index.tsx327
-rw-r--r--src/view/com/util/post-embeds/types.ts9
27 files changed, 6 insertions, 3767 deletions
diff --git a/src/view/com/util/Views.web.tsx b/src/view/com/util/Views.web.tsx
index 6a11c7eaa..9a3e8a4ae 100644
--- a/src/view/com/util/Views.web.tsx
+++ b/src/view/com/util/Views.web.tsx
@@ -14,12 +14,12 @@
 
 import React from 'react'
 import {
-  FlatList,
-  FlatListProps,
-  ScrollViewProps,
+  type FlatList,
+  type FlatListProps,
+  type ScrollViewProps,
   StyleSheet,
   View,
-  ViewProps,
+  type ViewProps,
 } from 'react-native'
 import Animated from 'react-native-reanimated'
 
diff --git a/src/view/com/util/images/Gallery.tsx b/src/view/com/util/images/Gallery.tsx
index 1d35c88c5..323264ea4 100644
--- a/src/view/com/util/images/Gallery.tsx
+++ b/src/view/com/util/images/Gallery.tsx
@@ -8,9 +8,9 @@ import type React from 'react'
 
 import {type Dimensions} from '#/lib/media/types'
 import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
-import {PostEmbedViewContext} from '#/view/com/util/post-embeds/types'
 import {atoms as a, useTheme} from '#/alf'
 import {MediaInsetBorder} from '#/components/MediaInsetBorder'
+import {PostEmbedViewContext} from '#/components/Post/Embed/types'
 import {Text} from '#/components/Typography'
 
 type EventFunction = (index: number) => void
diff --git a/src/view/com/util/images/ImageLayoutGrid.tsx b/src/view/com/util/images/ImageLayoutGrid.tsx
index b91d7a7ad..757d952a1 100644
--- a/src/view/com/util/images/ImageLayoutGrid.tsx
+++ b/src/view/com/util/images/ImageLayoutGrid.tsx
@@ -3,8 +3,8 @@ import {type StyleProp, StyleSheet, View, type ViewStyle} from 'react-native'
 import {type AnimatedRef, useAnimatedRef} from 'react-native-reanimated'
 import {type AppBskyEmbedImages} from '@atproto/api'
 
-import {PostEmbedViewContext} from '#/view/com/util/post-embeds/types'
 import {atoms as a, useBreakpoints} from '#/alf'
+import {PostEmbedViewContext} from '#/components/Post/Embed/types'
 import {type Dimensions} from '../../lightbox/ImageViewing/@types'
 import {GalleryItem} from './Gallery'
 
diff --git a/src/view/com/util/post-embeds/ActiveVideoWebContext.tsx b/src/view/com/util/post-embeds/ActiveVideoWebContext.tsx
deleted file mode 100644
index a038403b2..000000000
--- a/src/view/com/util/post-embeds/ActiveVideoWebContext.tsx
+++ /dev/null
@@ -1,114 +0,0 @@
-import React, {
-  useCallback,
-  useEffect,
-  useId,
-  useMemo,
-  useRef,
-  useState,
-} from 'react'
-import {useWindowDimensions} from 'react-native'
-
-import {isNative, isWeb} from '#/platform/detection'
-
-const Context = React.createContext<{
-  activeViewId: string | null
-  setActiveView: (viewId: string) => void
-  sendViewPosition: (viewId: string, y: number) => void
-} | null>(null)
-
-export function Provider({children}: {children: React.ReactNode}) {
-  if (!isWeb) {
-    throw new Error('ActiveVideoWebContext may only be used on web.')
-  }
-
-  const [activeViewId, setActiveViewId] = useState<string | null>(null)
-  const activeViewLocationRef = useRef(Infinity)
-  const {height: windowHeight} = useWindowDimensions()
-
-  // minimising re-renders by using refs
-  const manuallySetRef = useRef(false)
-  const activeViewIdRef = useRef(activeViewId)
-  useEffect(() => {
-    activeViewIdRef.current = activeViewId
-  }, [activeViewId])
-
-  const setActiveView = useCallback(
-    (viewId: string) => {
-      setActiveViewId(viewId)
-      manuallySetRef.current = true
-      // we don't know the exact position, but it's definitely on screen
-      // so just guess that it's in the middle. Any value is fine
-      // so long as it's not offscreen
-      activeViewLocationRef.current = windowHeight / 2
-    },
-    [windowHeight],
-  )
-
-  const sendViewPosition = useCallback(
-    (viewId: string, y: number) => {
-      if (isNative) return
-
-      if (viewId === activeViewIdRef.current) {
-        activeViewLocationRef.current = y
-      } else {
-        if (
-          distanceToIdealPosition(y) <
-          distanceToIdealPosition(activeViewLocationRef.current)
-        ) {
-          // if the old view was manually set, only usurp if the old view is offscreen
-          if (
-            manuallySetRef.current &&
-            withinViewport(activeViewLocationRef.current)
-          ) {
-            return
-          }
-
-          setActiveViewId(viewId)
-          activeViewLocationRef.current = y
-          manuallySetRef.current = false
-        }
-      }
-
-      function distanceToIdealPosition(yPos: number) {
-        return Math.abs(yPos - windowHeight / 2.5)
-      }
-
-      function withinViewport(yPos: number) {
-        return yPos > 0 && yPos < windowHeight
-      }
-    },
-    [windowHeight],
-  )
-
-  const value = useMemo(
-    () => ({
-      activeViewId,
-      setActiveView,
-      sendViewPosition,
-    }),
-    [activeViewId, setActiveView, sendViewPosition],
-  )
-
-  return <Context.Provider value={value}>{children}</Context.Provider>
-}
-
-export function useActiveVideoWeb() {
-  const context = React.useContext(Context)
-  if (!context) {
-    throw new Error(
-      'useActiveVideoWeb must be used within a ActiveVideoWebProvider',
-    )
-  }
-
-  const {activeViewId, setActiveView, sendViewPosition} = context
-  const id = useId()
-
-  return {
-    active: activeViewId === id,
-    setActive: () => {
-      setActiveView(id)
-    },
-    currentActiveView: activeViewId,
-    sendPosition: (y: number) => sendViewPosition(id, y),
-  }
-}
diff --git a/src/view/com/util/post-embeds/ExternalGifEmbed.tsx b/src/view/com/util/post-embeds/ExternalGifEmbed.tsx
deleted file mode 100644
index 39c1d109e..000000000
--- a/src/view/com/util/post-embeds/ExternalGifEmbed.tsx
+++ /dev/null
@@ -1,147 +0,0 @@
-import React from 'react'
-import {ActivityIndicator, GestureResponderEvent, Pressable} from 'react-native'
-import {Image} from 'expo-image'
-import {AppBskyEmbedExternal} from '@atproto/api'
-import {msg} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-
-import {EmbedPlayerParams} from '#/lib/strings/embed-player'
-import {isIOS, isNative, isWeb} from '#/platform/detection'
-import {useExternalEmbedsPrefs} from '#/state/preferences'
-import {atoms as a, useTheme} from '#/alf'
-import {useDialogControl} from '#/components/Dialog'
-import {EmbedConsentDialog} from '#/components/dialogs/EmbedConsent'
-import {Fill} from '#/components/Fill'
-import {PlayButtonIcon} from '#/components/video/PlayButtonIcon'
-
-export function ExternalGifEmbed({
-  link,
-  params,
-}: {
-  link: AppBskyEmbedExternal.ViewExternal
-  params: EmbedPlayerParams
-}) {
-  const t = useTheme()
-  const externalEmbedsPrefs = useExternalEmbedsPrefs()
-  const {_} = useLingui()
-  const consentDialogControl = useDialogControl()
-
-  // Tracking if the placer has been activated
-  const [isPlayerActive, setIsPlayerActive] = React.useState(false)
-  // Tracking whether the gif has been loaded yet
-  const [isPrefetched, setIsPrefetched] = React.useState(false)
-  // Tracking whether the image is animating
-  const [isAnimating, setIsAnimating] = React.useState(true)
-
-  // Used for controlling animation
-  const imageRef = React.useRef<Image>(null)
-
-  const load = React.useCallback(() => {
-    setIsPlayerActive(true)
-    Image.prefetch(params.playerUri).then(() => {
-      // Replace the image once it's fetched
-      setIsPrefetched(true)
-    })
-  }, [params.playerUri])
-
-  const onPlayPress = React.useCallback(
-    (event: GestureResponderEvent) => {
-      // Don't propagate on web
-      event.preventDefault()
-
-      // Show consent if this is the first load
-      if (externalEmbedsPrefs?.[params.source] === undefined) {
-        consentDialogControl.open()
-        return
-      }
-      // If the player isn't active, we want to activate it and prefetch the gif
-      if (!isPlayerActive) {
-        load()
-        return
-      }
-      // Control animation on native
-      setIsAnimating(prev => {
-        if (prev) {
-          if (isNative) {
-            imageRef.current?.stopAnimating()
-          }
-          return false
-        } else {
-          if (isNative) {
-            imageRef.current?.startAnimating()
-          }
-          return true
-        }
-      })
-    },
-    [
-      consentDialogControl,
-      externalEmbedsPrefs,
-      isPlayerActive,
-      load,
-      params.source,
-    ],
-  )
-
-  return (
-    <>
-      <EmbedConsentDialog
-        control={consentDialogControl}
-        source={params.source}
-        onAccept={load}
-      />
-
-      <Pressable
-        style={[
-          {height: 300},
-          a.w_full,
-          a.overflow_hidden,
-          {
-            borderBottomLeftRadius: 0,
-            borderBottomRightRadius: 0,
-          },
-        ]}
-        onPress={onPlayPress}
-        accessibilityRole="button"
-        accessibilityHint={_(msg`Plays the GIF`)}
-        accessibilityLabel={_(msg`Play ${link.title}`)}>
-        <Image
-          source={{
-            uri:
-              !isPrefetched || (isWeb && !isAnimating)
-                ? link.thumb
-                : params.playerUri,
-          }} // Web uses the thumb to control playback
-          style={{flex: 1}}
-          ref={imageRef}
-          autoplay={isAnimating}
-          contentFit="contain"
-          accessibilityIgnoresInvertColors
-          accessibilityLabel={link.title}
-          accessibilityHint={link.title}
-          cachePolicy={isIOS ? 'disk' : 'memory-disk'} // cant control playback with memory-disk on ios
-        />
-
-        {(!isPrefetched || !isAnimating) && (
-          <Fill style={[a.align_center, a.justify_center]}>
-            <Fill
-              style={[
-                t.name === 'light' ? t.atoms.bg_contrast_975 : t.atoms.bg,
-                {
-                  opacity: 0.3,
-                },
-              ]}
-            />
-
-            {!isAnimating || !isPlayerActive ? ( // Play button when not animating or not active
-              <PlayButtonIcon />
-            ) : (
-              // Activity indicator while gif loads
-              <ActivityIndicator size="large" color="white" />
-            )}
-          </Fill>
-        )}
-      </Pressable>
-    </>
-  )
-}
diff --git a/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx b/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx
deleted file mode 100644
index 7ca11f60d..000000000
--- a/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx
+++ /dev/null
@@ -1,182 +0,0 @@
-import React, {useCallback} from 'react'
-import {type StyleProp, View, type ViewStyle} from 'react-native'
-import {Image} from 'expo-image'
-import {type AppBskyEmbedExternal} from '@atproto/api'
-import {msg} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-
-import {parseAltFromGIFDescription} from '#/lib/gif-alt-text'
-import {useHaptics} from '#/lib/haptics'
-import {shareUrl} from '#/lib/sharing'
-import {parseEmbedPlayerFromUrl} from '#/lib/strings/embed-player'
-import {toNiceDomain} from '#/lib/strings/url-helpers'
-import {isNative} from '#/platform/detection'
-import {useExternalEmbedsPrefs} from '#/state/preferences'
-import {ExternalGifEmbed} from '#/view/com/util/post-embeds/ExternalGifEmbed'
-import {ExternalPlayer} from '#/view/com/util/post-embeds/ExternalPlayerEmbed'
-import {GifEmbed} from '#/view/com/util/post-embeds/GifEmbed'
-import {atoms as a, useTheme} from '#/alf'
-import {Divider} from '#/components/Divider'
-import {Earth_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe'
-import {Link} from '#/components/Link'
-import {Text} from '#/components/Typography'
-
-export const ExternalLinkEmbed = ({
-  link,
-  onOpen,
-  style,
-  hideAlt,
-}: {
-  link: AppBskyEmbedExternal.ViewExternal
-  onOpen?: () => void
-  style?: StyleProp<ViewStyle>
-  hideAlt?: boolean
-}) => {
-  const {_} = useLingui()
-  const t = useTheme()
-  const playHaptic = useHaptics()
-  const externalEmbedPrefs = useExternalEmbedsPrefs()
-  const niceUrl = toNiceDomain(link.uri)
-  const imageUri = link.thumb
-  const embedPlayerParams = React.useMemo(() => {
-    const params = parseEmbedPlayerFromUrl(link.uri)
-
-    if (params && externalEmbedPrefs?.[params.source] !== 'hide') {
-      return params
-    }
-  }, [link.uri, externalEmbedPrefs])
-  const hasMedia = Boolean(imageUri || embedPlayerParams)
-
-  const onPress = useCallback(() => {
-    playHaptic('Light')
-    onOpen?.()
-  }, [playHaptic, onOpen])
-
-  const onShareExternal = useCallback(() => {
-    if (link.uri && isNative) {
-      playHaptic('Heavy')
-      shareUrl(link.uri)
-    }
-  }, [link.uri, playHaptic])
-
-  if (embedPlayerParams?.source === 'tenor') {
-    const parsedAlt = parseAltFromGIFDescription(link.description)
-    return (
-      <View style={style}>
-        <GifEmbed
-          params={embedPlayerParams}
-          thumb={link.thumb}
-          altText={parsedAlt.alt}
-          isPreferredAltText={parsedAlt.isPreferred}
-          hideAlt={hideAlt}
-        />
-      </View>
-    )
-  }
-
-  return (
-    <Link
-      label={link.title || _(msg`Open link to ${niceUrl}`)}
-      to={link.uri}
-      shouldProxy={true}
-      onPress={onPress}
-      onLongPress={onShareExternal}>
-      {({hovered}) => (
-        <View
-          style={[
-            a.transition_color,
-            a.flex_col,
-            a.rounded_md,
-            a.overflow_hidden,
-            a.w_full,
-            a.border,
-            style,
-            hovered
-              ? t.atoms.border_contrast_high
-              : t.atoms.border_contrast_low,
-          ]}>
-          {imageUri && !embedPlayerParams ? (
-            <Image
-              style={{
-                aspectRatio: 1.91,
-              }}
-              source={{uri: imageUri}}
-              accessibilityIgnoresInvertColors
-            />
-          ) : undefined}
-
-          {embedPlayerParams?.isGif ? (
-            <ExternalGifEmbed link={link} params={embedPlayerParams} />
-          ) : embedPlayerParams ? (
-            <ExternalPlayer link={link} params={embedPlayerParams} />
-          ) : undefined}
-
-          <View
-            style={[
-              a.flex_1,
-              a.pt_sm,
-              {gap: 3},
-              hasMedia && a.border_t,
-              hovered
-                ? t.atoms.border_contrast_high
-                : t.atoms.border_contrast_low,
-            ]}>
-            <View style={[{gap: 3}, a.pb_xs, a.px_md]}>
-              {!embedPlayerParams?.isGif && !embedPlayerParams?.dimensions && (
-                <Text
-                  emoji
-                  numberOfLines={3}
-                  style={[a.text_md, a.font_bold, a.leading_snug]}>
-                  {link.title || link.uri}
-                </Text>
-              )}
-              {link.description ? (
-                <Text
-                  emoji
-                  numberOfLines={link.thumb ? 2 : 4}
-                  style={[a.text_sm, a.leading_snug]}>
-                  {link.description}
-                </Text>
-              ) : undefined}
-            </View>
-            <View style={[a.px_md]}>
-              <Divider />
-              <View
-                style={[
-                  a.flex_row,
-                  a.align_center,
-                  a.gap_2xs,
-                  a.pb_sm,
-                  {
-                    paddingTop: 6, // off menu
-                  },
-                ]}>
-                <Globe
-                  size="xs"
-                  style={[
-                    a.transition_color,
-                    hovered
-                      ? t.atoms.text_contrast_medium
-                      : t.atoms.text_contrast_low,
-                  ]}
-                />
-                <Text
-                  numberOfLines={1}
-                  style={[
-                    a.transition_color,
-                    a.text_xs,
-                    a.leading_snug,
-                    hovered
-                      ? t.atoms.text_contrast_high
-                      : t.atoms.text_contrast_medium,
-                  ]}>
-                  {toNiceDomain(link.uri)}
-                </Text>
-              </View>
-            </View>
-          </View>
-        </View>
-      )}
-    </Link>
-  )
-}
diff --git a/src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx b/src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx
deleted file mode 100644
index e78abdf17..000000000
--- a/src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx
+++ /dev/null
@@ -1,281 +0,0 @@
-import React from 'react'
-import {
-  ActivityIndicator,
-  GestureResponderEvent,
-  Pressable,
-  StyleSheet,
-  useWindowDimensions,
-  View,
-} from 'react-native'
-import Animated, {
-  measure,
-  runOnJS,
-  useAnimatedRef,
-  useFrameCallback,
-} from 'react-native-reanimated'
-import {useSafeAreaInsets} from 'react-native-safe-area-context'
-import {WebView} from 'react-native-webview'
-import {Image} from 'expo-image'
-import {AppBskyEmbedExternal} from '@atproto/api'
-import {msg} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-import {useNavigation} from '@react-navigation/native'
-
-import {NavigationProp} from '#/lib/routes/types'
-import {EmbedPlayerParams, getPlayerAspect} from '#/lib/strings/embed-player'
-import {isNative} from '#/platform/detection'
-import {useExternalEmbedsPrefs} from '#/state/preferences'
-import {atoms as a, useTheme} from '#/alf'
-import {useDialogControl} from '#/components/Dialog'
-import {EmbedConsentDialog} from '#/components/dialogs/EmbedConsent'
-import {Fill} from '#/components/Fill'
-import {PlayButtonIcon} from '#/components/video/PlayButtonIcon'
-import {EventStopper} from '../EventStopper'
-
-interface ShouldStartLoadRequest {
-  url: string
-}
-
-// This renders the overlay when the player is either inactive or loading as a separate layer
-function PlaceholderOverlay({
-  isLoading,
-  isPlayerActive,
-  onPress,
-}: {
-  isLoading: boolean
-  isPlayerActive: boolean
-  onPress: (event: GestureResponderEvent) => void
-}) {
-  const {_} = useLingui()
-
-  // If the player is active and not loading, we don't want to show the overlay.
-  if (isPlayerActive && !isLoading) return null
-
-  return (
-    <View style={[a.absolute, a.inset_0, styles.overlayLayer]}>
-      <Pressable
-        accessibilityRole="button"
-        accessibilityLabel={_(msg`Play Video`)}
-        accessibilityHint={_(msg`Plays the video`)}
-        onPress={onPress}
-        style={[styles.overlayContainer]}>
-        {!isPlayerActive ? (
-          <PlayButtonIcon />
-        ) : (
-          <ActivityIndicator size="large" color="white" />
-        )}
-      </Pressable>
-    </View>
-  )
-}
-
-// This renders the webview/youtube player as a separate layer
-function Player({
-  params,
-  onLoad,
-  isPlayerActive,
-}: {
-  isPlayerActive: boolean
-  params: EmbedPlayerParams
-  onLoad: () => void
-}) {
-  // ensures we only load what's requested
-  // when it's a youtube video, we need to allow both bsky.app and youtube.com
-  const onShouldStartLoadWithRequest = React.useCallback(
-    (event: ShouldStartLoadRequest) =>
-      event.url === params.playerUri ||
-      (params.source.startsWith('youtube') &&
-        event.url.includes('www.youtube.com')),
-    [params.playerUri, params.source],
-  )
-
-  // Don't show the player until it is active
-  if (!isPlayerActive) return null
-
-  return (
-    <EventStopper style={[a.absolute, a.inset_0, styles.playerLayer]}>
-      <WebView
-        javaScriptEnabled={true}
-        onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
-        mediaPlaybackRequiresUserAction={false}
-        allowsInlineMediaPlayback
-        bounces={false}
-        allowsFullscreenVideo
-        nestedScrollEnabled
-        source={{uri: params.playerUri}}
-        onLoad={onLoad}
-        style={styles.webview}
-        setSupportMultipleWindows={false} // Prevent any redirects from opening a new window (ads)
-      />
-    </EventStopper>
-  )
-}
-
-// This renders the player area and handles the logic for when to show the player and when to show the overlay
-export function ExternalPlayer({
-  link,
-  params,
-}: {
-  link: AppBskyEmbedExternal.ViewExternal
-  params: EmbedPlayerParams
-}) {
-  const t = useTheme()
-  const navigation = useNavigation<NavigationProp>()
-  const insets = useSafeAreaInsets()
-  const windowDims = useWindowDimensions()
-  const externalEmbedsPrefs = useExternalEmbedsPrefs()
-  const consentDialogControl = useDialogControl()
-
-  const [isPlayerActive, setPlayerActive] = React.useState(false)
-  const [isLoading, setIsLoading] = React.useState(true)
-
-  const aspect = React.useMemo(() => {
-    return getPlayerAspect({
-      type: params.type,
-      width: windowDims.width,
-      hasThumb: !!link.thumb,
-    })
-  }, [params.type, windowDims.width, link.thumb])
-
-  const viewRef = useAnimatedRef()
-  const frameCallback = useFrameCallback(() => {
-    const measurement = measure(viewRef)
-    if (!measurement) return
-
-    const {height: winHeight, width: winWidth} = windowDims
-
-    // Get the proper screen height depending on what is going on
-    const realWinHeight = isNative // If it is native, we always want the larger number
-      ? winHeight > winWidth
-        ? winHeight
-        : winWidth
-      : winHeight // On web, we always want the actual screen height
-
-    const top = measurement.pageY
-    const bot = measurement.pageY + measurement.height
-
-    // We can use the same logic on all platforms against the screenHeight that we get above
-    const isVisible = top <= realWinHeight - insets.bottom && bot >= insets.top
-
-    if (!isVisible) {
-      runOnJS(setPlayerActive)(false)
-    }
-  }, false) // False here disables autostarting the callback
-
-  // watch for leaving the viewport due to scrolling
-  React.useEffect(() => {
-    // We don't want to do anything if the player isn't active
-    if (!isPlayerActive) return
-
-    // Interval for scrolling works in most cases, However, for twitch embeds, if we navigate away from the screen the webview will
-    // continue playing. We need to watch for the blur event
-    const unsubscribe = navigation.addListener('blur', () => {
-      setPlayerActive(false)
-    })
-
-    // Start watching for changes
-    frameCallback.setActive(true)
-
-    return () => {
-      unsubscribe()
-      frameCallback.setActive(false)
-    }
-  }, [navigation, isPlayerActive, frameCallback])
-
-  const onLoad = React.useCallback(() => {
-    setIsLoading(false)
-  }, [])
-
-  const onPlayPress = React.useCallback(
-    (event: GestureResponderEvent) => {
-      // Prevent this from propagating upward on web
-      event.preventDefault()
-
-      if (externalEmbedsPrefs?.[params.source] === undefined) {
-        consentDialogControl.open()
-        return
-      }
-
-      setPlayerActive(true)
-    },
-    [externalEmbedsPrefs, consentDialogControl, params.source],
-  )
-
-  const onAcceptConsent = React.useCallback(() => {
-    setPlayerActive(true)
-  }, [])
-
-  return (
-    <>
-      <EmbedConsentDialog
-        control={consentDialogControl}
-        source={params.source}
-        onAccept={onAcceptConsent}
-      />
-
-      <Animated.View
-        ref={viewRef}
-        collapsable={false}
-        style={[aspect, a.overflow_hidden]}>
-        {link.thumb && (!isPlayerActive || isLoading) ? (
-          <>
-            <Image
-              style={[a.flex_1]}
-              source={{uri: link.thumb}}
-              accessibilityIgnoresInvertColors
-            />
-            <Fill
-              style={[
-                t.name === 'light' ? t.atoms.bg_contrast_975 : t.atoms.bg,
-                {
-                  opacity: 0.3,
-                },
-              ]}
-            />
-          </>
-        ) : (
-          <Fill
-            style={[
-              {
-                backgroundColor:
-                  t.name === 'light' ? t.palette.contrast_975 : 'black',
-                opacity: 0.3,
-              },
-            ]}
-          />
-        )}
-        <PlaceholderOverlay
-          isLoading={isLoading}
-          isPlayerActive={isPlayerActive}
-          onPress={onPlayPress}
-        />
-        <Player
-          isPlayerActive={isPlayerActive}
-          params={params}
-          onLoad={onLoad}
-        />
-      </Animated.View>
-    </>
-  )
-}
-
-const styles = StyleSheet.create({
-  overlayContainer: {
-    flex: 1,
-    justifyContent: 'center',
-    alignItems: 'center',
-  },
-  overlayLayer: {
-    zIndex: 2,
-  },
-  playerLayer: {
-    zIndex: 3,
-  },
-  webview: {
-    backgroundColor: 'transparent',
-  },
-  gifContainer: {
-    width: '100%',
-    overflow: 'hidden',
-  },
-})
diff --git a/src/view/com/util/post-embeds/GifEmbed.tsx b/src/view/com/util/post-embeds/GifEmbed.tsx
deleted file mode 100644
index a839294f1..000000000
--- a/src/view/com/util/post-embeds/GifEmbed.tsx
+++ /dev/null
@@ -1,224 +0,0 @@
-import React from 'react'
-import {
-  Pressable,
-  StyleProp,
-  StyleSheet,
-  TouchableOpacity,
-  View,
-  ViewStyle,
-} from 'react-native'
-import {msg, Trans} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-
-import {HITSLOP_20} from '#/lib/constants'
-import {EmbedPlayerParams} from '#/lib/strings/embed-player'
-import {isWeb} from '#/platform/detection'
-import {useAutoplayDisabled} from '#/state/preferences'
-import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
-import {atoms as a, useTheme} from '#/alf'
-import {Fill} from '#/components/Fill'
-import {Loader} from '#/components/Loader'
-import * as Prompt from '#/components/Prompt'
-import {Text} from '#/components/Typography'
-import {PlayButtonIcon} from '#/components/video/PlayButtonIcon'
-import {GifView} from '../../../../../modules/expo-bluesky-gif-view'
-import {GifViewStateChangeEvent} from '../../../../../modules/expo-bluesky-gif-view/src/GifView.types'
-
-function PlaybackControls({
-  onPress,
-  isPlaying,
-  isLoaded,
-}: {
-  onPress: () => void
-  isPlaying: boolean
-  isLoaded: boolean
-}) {
-  const {_} = useLingui()
-  const t = useTheme()
-
-  return (
-    <Pressable
-      accessibilityRole="button"
-      accessibilityHint={_(msg`Plays or pauses the GIF`)}
-      accessibilityLabel={isPlaying ? _(msg`Pause`) : _(msg`Play`)}
-      style={[
-        a.absolute,
-        a.align_center,
-        a.justify_center,
-        !isLoaded && a.border,
-        t.atoms.border_contrast_medium,
-        a.inset_0,
-        a.w_full,
-        a.h_full,
-        {
-          zIndex: 2,
-          backgroundColor: !isLoaded
-            ? t.atoms.bg_contrast_25.backgroundColor
-            : undefined,
-        },
-      ]}
-      onPress={onPress}>
-      {!isLoaded ? (
-        <View>
-          <View style={[a.align_center, a.justify_center]}>
-            <Loader size="xl" />
-          </View>
-        </View>
-      ) : !isPlaying ? (
-        <PlayButtonIcon />
-      ) : undefined}
-    </Pressable>
-  )
-}
-
-export function GifEmbed({
-  params,
-  thumb,
-  altText,
-  isPreferredAltText,
-  hideAlt,
-  style = {width: '100%'},
-}: {
-  params: EmbedPlayerParams
-  thumb: string | undefined
-  altText: string
-  isPreferredAltText: boolean
-  hideAlt?: boolean
-  style?: StyleProp<ViewStyle>
-}) {
-  const t = useTheme()
-  const {_} = useLingui()
-  const autoplayDisabled = useAutoplayDisabled()
-
-  const playerRef = React.useRef<GifView>(null)
-
-  const [playerState, setPlayerState] = React.useState<{
-    isPlaying: boolean
-    isLoaded: boolean
-  }>({
-    isPlaying: !autoplayDisabled,
-    isLoaded: false,
-  })
-
-  const onPlayerStateChange = React.useCallback(
-    (e: GifViewStateChangeEvent) => {
-      setPlayerState(e.nativeEvent)
-    },
-    [],
-  )
-
-  const onPress = React.useCallback(() => {
-    playerRef.current?.toggleAsync()
-  }, [])
-
-  return (
-    <View
-      style={[
-        a.rounded_md,
-        a.overflow_hidden,
-        a.border,
-        t.atoms.border_contrast_low,
-        {aspectRatio: params.dimensions!.width / params.dimensions!.height},
-        style,
-      ]}>
-      <View
-        style={[
-          a.absolute,
-          /*
-           * Aspect ratio was being clipped weirdly on web -esb
-           */
-          {
-            top: -2,
-            bottom: -2,
-            left: -2,
-            right: -2,
-          },
-        ]}>
-        <PlaybackControls
-          onPress={onPress}
-          isPlaying={playerState.isPlaying}
-          isLoaded={playerState.isLoaded}
-        />
-        <GifView
-          source={params.playerUri}
-          placeholderSource={thumb}
-          style={[a.flex_1]}
-          autoplay={!autoplayDisabled}
-          onPlayerStateChange={onPlayerStateChange}
-          ref={playerRef}
-          accessibilityHint={_(msg`Animated GIF`)}
-          accessibilityLabel={altText}
-        />
-        {!playerState.isPlaying && (
-          <Fill
-            style={[
-              t.name === 'light' ? t.atoms.bg_contrast_975 : t.atoms.bg,
-              {
-                opacity: 0.3,
-              },
-            ]}
-          />
-        )}
-        {!hideAlt && isPreferredAltText && <AltText text={altText} />}
-      </View>
-    </View>
-  )
-}
-
-function AltText({text}: {text: string}) {
-  const control = Prompt.usePromptControl()
-  const largeAltBadge = useLargeAltBadgeEnabled()
-
-  const {_} = useLingui()
-  return (
-    <>
-      <TouchableOpacity
-        testID="altTextButton"
-        accessibilityRole="button"
-        accessibilityLabel={_(msg`Show alt text`)}
-        accessibilityHint=""
-        hitSlop={HITSLOP_20}
-        onPress={control.open}
-        style={styles.altContainer}>
-        <Text
-          style={[styles.alt, largeAltBadge && a.text_xs]}
-          accessible={false}>
-          <Trans>ALT</Trans>
-        </Text>
-      </TouchableOpacity>
-      <Prompt.Outer control={control}>
-        <Prompt.TitleText>
-          <Trans>Alt Text</Trans>
-        </Prompt.TitleText>
-        <Prompt.DescriptionText selectable>{text}</Prompt.DescriptionText>
-        <Prompt.Actions>
-          <Prompt.Action
-            onPress={() => control.close()}
-            cta={_(msg`Close`)}
-            color="secondary"
-          />
-        </Prompt.Actions>
-      </Prompt.Outer>
-    </>
-  )
-}
-
-const styles = StyleSheet.create({
-  altContainer: {
-    backgroundColor: 'rgba(0, 0, 0, 0.75)',
-    borderRadius: 6,
-    paddingHorizontal: isWeb ? 8 : 6,
-    paddingVertical: isWeb ? 6 : 3,
-    position: 'absolute',
-    // Related to margin/gap hack. This keeps the alt label in the same position
-    // on all platforms
-    right: isWeb ? 8 : 5,
-    bottom: isWeb ? 8 : 5,
-    zIndex: 2,
-  },
-  alt: {
-    color: 'white',
-    fontSize: isWeb ? 10 : 7,
-    fontWeight: '600',
-  },
-})
diff --git a/src/view/com/util/post-embeds/QuoteEmbed.tsx b/src/view/com/util/post-embeds/QuoteEmbed.tsx
deleted file mode 100644
index f788af1f8..000000000
--- a/src/view/com/util/post-embeds/QuoteEmbed.tsx
+++ /dev/null
@@ -1,337 +0,0 @@
-import React from 'react'
-import {
-  StyleProp,
-  StyleSheet,
-  TouchableOpacity,
-  View,
-  ViewStyle,
-} from 'react-native'
-import {
-  AppBskyEmbedExternal,
-  AppBskyEmbedImages,
-  AppBskyEmbedRecord,
-  AppBskyEmbedRecordWithMedia,
-  AppBskyEmbedVideo,
-  AppBskyFeedDefs,
-  AppBskyFeedPost,
-  moderatePost,
-  ModerationDecision,
-  RichText as RichTextAPI,
-} from '@atproto/api'
-import {AtUri} from '@atproto/api'
-import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
-import {msg, Trans} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-import {useQueryClient} from '@tanstack/react-query'
-
-import {HITSLOP_20} from '#/lib/constants'
-import {usePalette} from '#/lib/hooks/usePalette'
-import {InfoCircleIcon} from '#/lib/icons'
-import {makeProfileLink} from '#/lib/routes/links'
-import {s} from '#/lib/styles'
-import {useModerationOpts} from '#/state/preferences/moderation-opts'
-import {precacheProfile} from '#/state/queries/profile'
-import {useResolveLinkQuery} from '#/state/queries/resolve-link'
-import {useSession} from '#/state/session'
-import {atoms as a, useTheme} from '#/alf'
-import {RichText} from '#/components/RichText'
-import {SubtleWebHover} from '#/components/SubtleWebHover'
-import * as bsky from '#/types/bsky'
-import {ContentHider} from '../../../../components/moderation/ContentHider'
-import {PostAlerts} from '../../../../components/moderation/PostAlerts'
-import {Link} from '../Link'
-import {PostMeta} from '../PostMeta'
-import {Text} from '../text/Text'
-import {PostEmbeds} from '.'
-import {QuoteEmbedViewContext} from './types'
-
-export function MaybeQuoteEmbed({
-  embed,
-  onOpen,
-  style,
-  allowNestedQuotes,
-  viewContext,
-}: {
-  embed: AppBskyEmbedRecord.View
-  onOpen?: () => void
-  style?: StyleProp<ViewStyle>
-  allowNestedQuotes?: boolean
-  viewContext?: QuoteEmbedViewContext
-}) {
-  const t = useTheme()
-  const pal = usePalette('default')
-  const {currentAccount} = useSession()
-  if (
-    AppBskyEmbedRecord.isViewRecord(embed.record) &&
-    AppBskyFeedPost.isRecord(embed.record.value) &&
-    AppBskyFeedPost.validateRecord(embed.record.value).success
-  ) {
-    return (
-      <QuoteEmbedModerated
-        viewRecord={embed.record}
-        onOpen={onOpen}
-        style={style}
-        allowNestedQuotes={allowNestedQuotes}
-        viewContext={viewContext}
-      />
-    )
-  } else if (AppBskyEmbedRecord.isViewBlocked(embed.record)) {
-    return (
-      <View
-        style={[styles.errorContainer, a.border, t.atoms.border_contrast_low]}>
-        <InfoCircleIcon size={18} style={pal.text} />
-        <Text type="lg" style={pal.text}>
-          <Trans>Blocked</Trans>
-        </Text>
-      </View>
-    )
-  } else if (AppBskyEmbedRecord.isViewNotFound(embed.record)) {
-    return (
-      <View
-        style={[styles.errorContainer, a.border, t.atoms.border_contrast_low]}>
-        <InfoCircleIcon size={18} style={pal.text} />
-        <Text type="lg" style={pal.text}>
-          <Trans>Deleted</Trans>
-        </Text>
-      </View>
-    )
-  } else if (AppBskyEmbedRecord.isViewDetached(embed.record)) {
-    const isViewerOwner = currentAccount?.did
-      ? embed.record.uri.includes(currentAccount.did)
-      : false
-    return (
-      <View
-        style={[styles.errorContainer, a.border, t.atoms.border_contrast_low]}>
-        <InfoCircleIcon size={18} style={pal.text} />
-        <Text type="lg" style={pal.text}>
-          {isViewerOwner ? (
-            <Trans>Removed by you</Trans>
-          ) : (
-            <Trans>Removed by author</Trans>
-          )}
-        </Text>
-      </View>
-    )
-  }
-  return null
-}
-
-function QuoteEmbedModerated({
-  viewRecord,
-  onOpen,
-  style,
-  allowNestedQuotes,
-  viewContext,
-}: {
-  viewRecord: AppBskyEmbedRecord.ViewRecord
-  onOpen?: () => void
-  style?: StyleProp<ViewStyle>
-  allowNestedQuotes?: boolean
-  viewContext?: QuoteEmbedViewContext
-}) {
-  const moderationOpts = useModerationOpts()
-  const postView = React.useMemo(
-    () => viewRecordToPostView(viewRecord),
-    [viewRecord],
-  )
-  const moderation = React.useMemo(() => {
-    return moderationOpts ? moderatePost(postView, moderationOpts) : undefined
-  }, [postView, moderationOpts])
-
-  return (
-    <QuoteEmbed
-      quote={postView}
-      moderation={moderation}
-      onOpen={onOpen}
-      style={style}
-      allowNestedQuotes={allowNestedQuotes}
-      viewContext={viewContext}
-    />
-  )
-}
-
-export function QuoteEmbed({
-  quote,
-  moderation,
-  onOpen,
-  style,
-  allowNestedQuotes,
-}: {
-  quote: AppBskyFeedDefs.PostView
-  moderation?: ModerationDecision
-  onOpen?: () => void
-  style?: StyleProp<ViewStyle>
-  allowNestedQuotes?: boolean
-  viewContext?: QuoteEmbedViewContext
-}) {
-  const t = useTheme()
-  const queryClient = useQueryClient()
-  const pal = usePalette('default')
-  const itemUrip = new AtUri(quote.uri)
-  const itemHref = makeProfileLink(quote.author, 'post', itemUrip.rkey)
-  const itemTitle = `Post by ${quote.author.handle}`
-
-  const richText = React.useMemo(() => {
-    if (
-      !bsky.dangerousIsType<AppBskyFeedPost.Record>(
-        quote.record,
-        AppBskyFeedPost.isRecord,
-      )
-    )
-      return undefined
-    const {text, facets} = quote.record
-    return text.trim()
-      ? new RichTextAPI({text: text, facets: facets})
-      : undefined
-  }, [quote.record])
-
-  const embed = React.useMemo(() => {
-    const e = quote.embed
-
-    if (allowNestedQuotes) {
-      return e
-    } else {
-      if (
-        AppBskyEmbedImages.isView(e) ||
-        AppBskyEmbedExternal.isView(e) ||
-        AppBskyEmbedVideo.isView(e)
-      ) {
-        return e
-      } else if (
-        AppBskyEmbedRecordWithMedia.isView(e) &&
-        (AppBskyEmbedImages.isView(e.media) ||
-          AppBskyEmbedExternal.isView(e.media) ||
-          AppBskyEmbedVideo.isView(e.media))
-      ) {
-        return e.media
-      }
-    }
-  }, [quote.embed, allowNestedQuotes])
-
-  const onBeforePress = React.useCallback(() => {
-    precacheProfile(queryClient, quote.author)
-    onOpen?.()
-  }, [queryClient, quote.author, onOpen])
-
-  const [hover, setHover] = React.useState(false)
-  return (
-    <View
-      onPointerEnter={() => {
-        setHover(true)
-      }}
-      onPointerLeave={() => {
-        setHover(false)
-      }}>
-      <ContentHider
-        modui={moderation?.ui('contentList')}
-        style={[
-          a.rounded_md,
-          a.p_md,
-          a.mt_sm,
-          a.border,
-          t.atoms.border_contrast_low,
-          style,
-        ]}
-        childContainerStyle={[a.pt_sm]}>
-        <SubtleWebHover hover={hover} />
-        <Link
-          hoverStyle={{borderColor: pal.colors.borderLinkHover}}
-          href={itemHref}
-          title={itemTitle}
-          onBeforePress={onBeforePress}>
-          <View pointerEvents="none">
-            <PostMeta
-              author={quote.author}
-              moderation={moderation}
-              showAvatar
-              postHref={itemHref}
-              timestamp={quote.indexedAt}
-            />
-          </View>
-          {moderation ? (
-            <PostAlerts
-              modui={moderation.ui('contentView')}
-              style={[a.py_xs]}
-            />
-          ) : null}
-          {richText ? (
-            <RichText
-              value={richText}
-              style={a.text_md}
-              numberOfLines={20}
-              disableLinks
-            />
-          ) : null}
-          {embed && <PostEmbeds embed={embed} moderation={moderation} />}
-        </Link>
-      </ContentHider>
-    </View>
-  )
-}
-
-export function QuoteX({onRemove}: {onRemove: () => void}) {
-  const {_} = useLingui()
-  return (
-    <TouchableOpacity
-      style={[
-        a.absolute,
-        a.p_xs,
-        a.rounded_full,
-        a.align_center,
-        a.justify_center,
-        {
-          top: 16,
-          right: 10,
-          backgroundColor: 'rgba(0, 0, 0, 0.75)',
-        },
-      ]}
-      onPress={onRemove}
-      accessibilityRole="button"
-      accessibilityLabel={_(msg`Remove quote`)}
-      accessibilityHint={_(msg`Removes quoted post`)}
-      onAccessibilityEscape={onRemove}
-      hitSlop={HITSLOP_20}>
-      <FontAwesomeIcon size={12} icon="xmark" style={s.white} />
-    </TouchableOpacity>
-  )
-}
-
-export function LazyQuoteEmbed({uri}: {uri: string}) {
-  const {data} = useResolveLinkQuery(uri)
-  const moderationOpts = useModerationOpts()
-  if (!data || data.type !== 'record' || data.kind !== 'post') {
-    return null
-  }
-  const moderation = moderationOpts
-    ? moderatePost(data.view, moderationOpts)
-    : undefined
-  return <QuoteEmbed quote={data.view} moderation={moderation} />
-}
-
-function viewRecordToPostView(
-  viewRecord: AppBskyEmbedRecord.ViewRecord,
-): AppBskyFeedDefs.PostView {
-  const {value, embeds, ...rest} = viewRecord
-  return {
-    ...rest,
-    $type: 'app.bsky.feed.defs#postView',
-    record: value,
-    embed: embeds?.[0],
-  }
-}
-
-const styles = StyleSheet.create({
-  errorContainer: {
-    flexDirection: 'row',
-    alignItems: 'center',
-    gap: 4,
-    borderRadius: 8,
-    marginTop: 8,
-    paddingVertical: 14,
-    paddingHorizontal: 14,
-    borderWidth: StyleSheet.hairlineWidth,
-  },
-  alert: {
-    marginBottom: 6,
-  },
-})
diff --git a/src/view/com/util/post-embeds/VideoEmbed.tsx b/src/view/com/util/post-embeds/VideoEmbed.tsx
deleted file mode 100644
index b45027089..000000000
--- a/src/view/com/util/post-embeds/VideoEmbed.tsx
+++ /dev/null
@@ -1,167 +0,0 @@
-import React, {useCallback, useState} from 'react'
-import {ActivityIndicator, View} from 'react-native'
-import {ImageBackground} from 'expo-image'
-import {AppBskyEmbedVideo} from '@atproto/api'
-import {msg, Trans} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-
-import {ConstrainedImage} from '#/view/com/util/images/AutoSizedImage'
-import {VideoEmbedInnerNative} from '#/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative'
-import {atoms as a, useTheme} from '#/alf'
-import {Button} from '#/components/Button'
-import {useThrottledValue} from '#/components/hooks/useThrottledValue'
-import {PlayButtonIcon} from '#/components/video/PlayButtonIcon'
-import {ErrorBoundary} from '../ErrorBoundary'
-import * as VideoFallback from './VideoEmbedInner/VideoFallback'
-
-interface Props {
-  embed: AppBskyEmbedVideo.View
-  crop?: 'none' | 'square' | 'constrained'
-}
-
-export function VideoEmbed({embed, crop}: Props) {
-  const t = useTheme()
-  const [key, setKey] = useState(0)
-
-  const renderError = useCallback(
-    (error: unknown) => (
-      <VideoError error={error} retry={() => setKey(key + 1)} />
-    ),
-    [key],
-  )
-
-  let aspectRatio: number | undefined
-  const dims = embed.aspectRatio
-  if (dims) {
-    aspectRatio = dims.width / dims.height
-    if (Number.isNaN(aspectRatio)) {
-      aspectRatio = undefined
-    }
-  }
-
-  let constrained: number | undefined
-  let max: number | undefined
-  if (aspectRatio !== undefined) {
-    const ratio = 1 / 2 // max of 1:2 ratio in feeds
-    constrained = Math.max(aspectRatio, ratio)
-    max = Math.max(aspectRatio, 0.25) // max of 1:4 in thread
-  }
-  const cropDisabled = crop === 'none'
-
-  const contents = (
-    <ErrorBoundary renderError={renderError} key={key}>
-      <InnerWrapper embed={embed} />
-    </ErrorBoundary>
-  )
-
-  return (
-    <View style={[a.pt_xs]}>
-      {cropDisabled ? (
-        <View
-          style={[
-            a.w_full,
-            a.overflow_hidden,
-            {aspectRatio: max ?? 1},
-            a.rounded_md,
-            a.overflow_hidden,
-            t.atoms.bg_contrast_25,
-          ]}>
-          {contents}
-        </View>
-      ) : (
-        <ConstrainedImage
-          fullBleed={crop === 'square'}
-          aspectRatio={constrained || 1}>
-          {contents}
-        </ConstrainedImage>
-      )}
-    </View>
-  )
-}
-
-function InnerWrapper({embed}: Props) {
-  const {_} = useLingui()
-  const ref = React.useRef<{togglePlayback: () => void}>(null)
-
-  const [status, setStatus] = React.useState<'playing' | 'paused' | 'pending'>(
-    'pending',
-  )
-  const [isLoading, setIsLoading] = React.useState(false)
-  const [isActive, setIsActive] = React.useState(false)
-  const showSpinner = useThrottledValue(isActive && isLoading, 100)
-
-  const showOverlay =
-    !isActive ||
-    isLoading ||
-    (status === 'paused' && !isActive) ||
-    status === 'pending'
-
-  React.useEffect(() => {
-    if (!isActive && status !== 'pending') {
-      setStatus('pending')
-    }
-  }, [isActive, status])
-
-  return (
-    <>
-      <VideoEmbedInnerNative
-        embed={embed}
-        setStatus={setStatus}
-        setIsLoading={setIsLoading}
-        setIsActive={setIsActive}
-        ref={ref}
-      />
-      <ImageBackground
-        source={{uri: embed.thumbnail}}
-        accessibilityIgnoresInvertColors
-        style={[
-          a.absolute,
-          a.inset_0,
-          {
-            backgroundColor: 'transparent', // If you don't add `backgroundColor` to the styles here,
-            // the play button won't show up on the first render on android 🥴😮‍💨
-            display: showOverlay ? 'flex' : 'none',
-          },
-        ]}
-        cachePolicy="memory-disk" // Preferring memory cache helps to avoid flicker when re-displaying on android
-      >
-        {showOverlay && (
-          <Button
-            style={[a.flex_1, a.align_center, a.justify_center]}
-            onPress={() => {
-              ref.current?.togglePlayback()
-            }}
-            label={_(msg`Play video`)}
-            color="secondary">
-            {showSpinner ? (
-              <View
-                style={[
-                  a.rounded_full,
-                  a.p_xs,
-                  a.align_center,
-                  a.justify_center,
-                ]}>
-                <ActivityIndicator size="large" color="white" />
-              </View>
-            ) : (
-              <PlayButtonIcon />
-            )}
-          </Button>
-        )}
-      </ImageBackground>
-    </>
-  )
-}
-
-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>
-  )
-}
diff --git a/src/view/com/util/post-embeds/VideoEmbed.web.tsx b/src/view/com/util/post-embeds/VideoEmbed.web.tsx
deleted file mode 100644
index b0ded6754..000000000
--- a/src/view/com/util/post-embeds/VideoEmbed.web.tsx
+++ /dev/null
@@ -1,207 +0,0 @@
-import React, {useCallback, useEffect, useRef, useState} from 'react'
-import {View} from 'react-native'
-import {AppBskyEmbedVideo} from '@atproto/api'
-import {msg} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-
-import {isFirefox} from '#/lib/browser'
-import {ConstrainedImage} from '#/view/com/util/images/AutoSizedImage'
-import {
-  HLSUnsupportedError,
-  VideoEmbedInnerWeb,
-  VideoNotFoundError,
-} from '#/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb'
-import {atoms as a} from '#/alf'
-import {useIsWithinMessage} from '#/components/dms/MessageContext'
-import {useFullscreen} from '#/components/hooks/useFullscreen'
-import {ErrorBoundary} from '../ErrorBoundary'
-import {useActiveVideoWeb} from './ActiveVideoWebContext'
-import * as VideoFallback from './VideoEmbedInner/VideoFallback'
-
-export function VideoEmbed({
-  embed,
-  crop,
-}: {
-  embed: AppBskyEmbedVideo.View
-  crop?: 'none' | 'square' | 'constrained'
-}) {
-  const ref = useRef<HTMLDivElement>(null)
-  const {active, setActive, sendPosition, currentActiveView} =
-    useActiveVideoWeb()
-  const [onScreen, setOnScreen] = useState(false)
-  const [isFullscreen] = useFullscreen()
-  const lastKnownTime = useRef<number | undefined>()
-
-  useEffect(() => {
-    if (!ref.current) return
-    if (isFullscreen && !isFirefox) return
-    const observer = new IntersectionObserver(
-      entries => {
-        const entry = entries[0]
-        if (!entry) return
-        setOnScreen(entry.isIntersecting)
-        sendPosition(
-          entry.boundingClientRect.y + entry.boundingClientRect.height / 2,
-        )
-      },
-      {threshold: 0.5},
-    )
-    observer.observe(ref.current)
-    return () => observer.disconnect()
-  }, [sendPosition, isFullscreen])
-
-  const [key, setKey] = useState(0)
-  const renderError = useCallback(
-    (error: unknown) => (
-      <VideoError error={error} retry={() => setKey(key + 1)} />
-    ),
-    [key],
-  )
-
-  let aspectRatio: number | undefined
-  const dims = embed.aspectRatio
-  if (dims) {
-    aspectRatio = dims.width / dims.height
-    if (Number.isNaN(aspectRatio)) {
-      aspectRatio = undefined
-    }
-  }
-
-  let constrained: number | undefined
-  let max: number | undefined
-  if (aspectRatio !== undefined) {
-    const ratio = 1 / 2 // max of 1:2 ratio in feeds
-    constrained = Math.max(aspectRatio, ratio)
-    max = Math.max(aspectRatio, 0.25) // max of 1:4 in thread
-  }
-  const cropDisabled = crop === 'none'
-
-  const contents = (
-    <div
-      ref={ref}
-      style={{display: 'flex', flex: 1, cursor: 'default'}}
-      onClick={evt => evt.stopPropagation()}>
-      <ErrorBoundary renderError={renderError} key={key}>
-        <ViewportObserver
-          sendPosition={sendPosition}
-          isAnyViewActive={currentActiveView !== null}>
-          <VideoEmbedInnerWeb
-            embed={embed}
-            active={active}
-            setActive={setActive}
-            onScreen={onScreen}
-            lastKnownTime={lastKnownTime}
-          />
-        </ViewportObserver>
-      </ErrorBoundary>
-    </div>
-  )
-
-  return (
-    <View style={[a.pt_xs]}>
-      {cropDisabled ? (
-        <View style={[a.w_full, a.overflow_hidden, {aspectRatio: max ?? 1}]}>
-          {contents}
-        </View>
-      ) : (
-        <ConstrainedImage
-          fullBleed={crop === 'square'}
-          aspectRatio={constrained || 1}>
-          {contents}
-        </ConstrainedImage>
-      )}
-    </View>
-  )
-}
-
-/**
- * Renders a 100vh tall div and watches it with an IntersectionObserver to
- * send the position of the div when it's near the screen.
- */
-function ViewportObserver({
-  children,
-  sendPosition,
-  isAnyViewActive,
-}: {
-  children: React.ReactNode
-  sendPosition: (position: number) => void
-  isAnyViewActive: boolean
-}) {
-  const ref = useRef<HTMLDivElement>(null)
-  const [nearScreen, setNearScreen] = useState(false)
-  const [isFullscreen] = useFullscreen()
-  const isWithinMessage = useIsWithinMessage()
-
-  // Send position when scrolling. This is done with an IntersectionObserver
-  // observing a div of 100vh height
-  useEffect(() => {
-    if (!ref.current) return
-    if (isFullscreen && !isFirefox) return
-    const observer = new IntersectionObserver(
-      entries => {
-        const entry = entries[0]
-        if (!entry) return
-        const position =
-          entry.boundingClientRect.y + entry.boundingClientRect.height / 2
-        sendPosition(position)
-        setNearScreen(entry.isIntersecting)
-      },
-      {threshold: Array.from({length: 101}, (_, i) => i / 100)},
-    )
-    observer.observe(ref.current)
-    return () => observer.disconnect()
-  }, [sendPosition, isFullscreen])
-
-  // In case scrolling hasn't started yet, send up the position
-  useEffect(() => {
-    if (ref.current && !isAnyViewActive) {
-      const rect = ref.current.getBoundingClientRect()
-      const position = rect.y + rect.height / 2
-      sendPosition(position)
-    }
-  }, [isAnyViewActive, sendPosition])
-
-  return (
-    <View style={[a.flex_1, a.flex_row]}>
-      {nearScreen && children}
-      <div
-        ref={ref}
-        style={{
-          // Don't escape bounds when in a message
-          ...(isWithinMessage
-            ? {top: 0, height: '100%'}
-            : {top: 'calc(50% - 50vh)', height: '100vh'}),
-          position: 'absolute',
-          left: '50%',
-          width: 1,
-          pointerEvents: 'none',
-        }}
-      />
-    </View>
-  )
-}
-
-function VideoError({error, retry}: {error: unknown; retry: () => void}) {
-  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>{text}</VideoFallback.Text>
-      {showRetryButton && <VideoFallback.RetryButton onPress={retry} />}
-    </VideoFallback.Container>
-  )
-}
diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/TimeIndicator.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/TimeIndicator.tsx
deleted file mode 100644
index 95401309f..000000000
--- a/src/view/com/util/post-embeds/VideoEmbedInner/TimeIndicator.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import {StyleProp, ViewStyle} from 'react-native'
-import {View} from 'react-native'
-import {msg, plural} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-
-import {atoms as a, useTheme} from '#/alf'
-import {Text} from '#/components/Typography'
-
-/**
- * Absolutely positioned time indicator showing how many seconds are remaining
- * Time is in seconds
- */
-export function TimeIndicator({
-  time,
-  style,
-}: {
-  time: number
-  style?: StyleProp<ViewStyle>
-}) {
-  const t = useTheme()
-  const {_} = useLingui()
-
-  if (isNaN(time)) {
-    return null
-  }
-
-  const minutes = Math.floor(time / 60)
-  const seconds = String(time % 60).padStart(2, '0')
-
-  return (
-    <View
-      pointerEvents="none"
-      accessibilityLabel={_(
-        msg`Time remaining: ${plural(Number(time) || 0, {
-          one: '# second',
-          other: '# seconds',
-        })}`,
-      )}
-      accessibilityHint=""
-      style={[
-        {
-          backgroundColor: 'rgba(0, 0, 0, 0.5)',
-          borderRadius: 6,
-          paddingHorizontal: 6,
-          paddingVertical: 3,
-          left: 6,
-          bottom: 6,
-          minHeight: 21,
-        },
-        a.absolute,
-        a.justify_center,
-        style,
-      ]}>
-      <Text
-        style={[
-          {color: t.palette.white, fontSize: 12, fontVariant: ['tabular-nums']},
-          a.font_bold,
-          {lineHeight: 1.25},
-        ]}>
-        {`${minutes}:${seconds}`}
-      </Text>
-    </View>
-  )
-}
diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx
deleted file mode 100644
index 8b44f5448..000000000
--- a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx
+++ /dev/null
@@ -1,210 +0,0 @@
-import React, {useRef} from 'react'
-import {Pressable, StyleProp, View, ViewStyle} from 'react-native'
-import {AppBskyEmbedVideo} from '@atproto/api'
-import {BlueskyVideoView} from '@haileyok/bluesky-video'
-import {msg} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-
-import {HITSLOP_30} from '#/lib/constants'
-import {useAutoplayDisabled} from '#/state/preferences'
-import {useVideoMuteState} from '#/view/com/util/post-embeds/VideoVolumeContext'
-import {atoms as a, useTheme} from '#/alf'
-import {useIsWithinMessage} from '#/components/dms/MessageContext'
-import {Mute_Stroke2_Corner0_Rounded as MuteIcon} from '#/components/icons/Mute'
-import {Pause_Filled_Corner0_Rounded as PauseIcon} from '#/components/icons/Pause'
-import {Play_Filled_Corner0_Rounded as PlayIcon} from '#/components/icons/Play'
-import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as UnmuteIcon} from '#/components/icons/Speaker'
-import {MediaInsetBorder} from '#/components/MediaInsetBorder'
-import {TimeIndicator} from './TimeIndicator'
-
-export const VideoEmbedInnerNative = React.forwardRef(
-  function VideoEmbedInnerNative(
-    {
-      embed,
-      setStatus,
-      setIsLoading,
-      setIsActive,
-    }: {
-      embed: AppBskyEmbedVideo.View
-      setStatus: (status: 'playing' | 'paused') => void
-      setIsLoading: (isLoading: boolean) => void
-      setIsActive: (isActive: boolean) => void
-    },
-    ref: React.Ref<{togglePlayback: () => void}>,
-  ) {
-    const {_} = useLingui()
-    const videoRef = useRef<BlueskyVideoView>(null)
-    const autoplayDisabled = useAutoplayDisabled()
-    const isWithinMessage = useIsWithinMessage()
-    const [muted, setMuted] = useVideoMuteState()
-
-    const [isPlaying, setIsPlaying] = React.useState(false)
-    const [timeRemaining, setTimeRemaining] = React.useState(0)
-    const [error, setError] = React.useState<string>()
-
-    React.useImperativeHandle(ref, () => ({
-      togglePlayback: () => {
-        videoRef.current?.togglePlayback()
-      },
-    }))
-
-    if (error) {
-      throw new Error(error)
-    }
-
-    return (
-      <View style={[a.flex_1, a.relative]}>
-        <BlueskyVideoView
-          url={embed.playlist}
-          autoplay={!autoplayDisabled && !isWithinMessage}
-          beginMuted={autoplayDisabled ? false : muted}
-          style={[a.rounded_sm]}
-          onActiveChange={e => {
-            setIsActive(e.nativeEvent.isActive)
-          }}
-          onLoadingChange={e => {
-            setIsLoading(e.nativeEvent.isLoading)
-          }}
-          onMutedChange={e => {
-            setMuted(e.nativeEvent.isMuted)
-          }}
-          onStatusChange={e => {
-            setStatus(e.nativeEvent.status)
-            setIsPlaying(e.nativeEvent.status === 'playing')
-          }}
-          onTimeRemainingChange={e => {
-            setTimeRemaining(e.nativeEvent.timeRemaining)
-          }}
-          onError={e => {
-            setError(e.nativeEvent.error)
-          }}
-          ref={videoRef}
-          accessibilityLabel={
-            embed.alt ? _(msg`Video: ${embed.alt}`) : _(msg`Video`)
-          }
-          accessibilityHint=""
-        />
-        <VideoControls
-          enterFullscreen={() => {
-            videoRef.current?.enterFullscreen(true)
-          }}
-          toggleMuted={() => {
-            videoRef.current?.toggleMuted()
-          }}
-          togglePlayback={() => {
-            videoRef.current?.togglePlayback()
-          }}
-          isPlaying={isPlaying}
-          timeRemaining={timeRemaining}
-        />
-        <MediaInsetBorder />
-      </View>
-    )
-  },
-)
-
-function VideoControls({
-  enterFullscreen,
-  toggleMuted,
-  togglePlayback,
-  timeRemaining,
-  isPlaying,
-}: {
-  enterFullscreen: () => void
-  toggleMuted: () => void
-  togglePlayback: () => void
-  timeRemaining: number
-  isPlaying: boolean
-}) {
-  const {_} = useLingui()
-  const t = useTheme()
-  const [muted] = useVideoMuteState()
-
-  // show countdown when:
-  // 1. timeRemaining is a number - was seeing NaNs
-  // 2. duration is greater than 0 - means metadata has loaded
-  // 3. we're less than 5 second into the video
-  const showTime = !isNaN(timeRemaining)
-
-  return (
-    <View style={[a.absolute, a.inset_0]}>
-      <Pressable
-        onPress={enterFullscreen}
-        style={a.flex_1}
-        accessibilityLabel={_(msg`Video`)}
-        accessibilityHint={_(msg`Enters full screen`)}
-        accessibilityRole="button"
-      />
-      <ControlButton
-        onPress={togglePlayback}
-        label={isPlaying ? _(msg`Pause`) : _(msg`Play`)}
-        accessibilityHint={_(msg`Plays or pauses the video`)}
-        style={{left: 6}}>
-        {isPlaying ? (
-          <PauseIcon width={13} fill={t.palette.white} />
-        ) : (
-          <PlayIcon width={13} fill={t.palette.white} />
-        )}
-      </ControlButton>
-      {showTime && <TimeIndicator time={timeRemaining} style={{left: 33}} />}
-
-      <ControlButton
-        onPress={toggleMuted}
-        label={
-          muted
-            ? _(msg({message: `Unmute`, context: 'video'}))
-            : _(msg({message: `Mute`, context: 'video'}))
-        }
-        accessibilityHint={_(msg`Toggles the sound`)}
-        style={{right: 6}}>
-        {muted ? (
-          <MuteIcon width={13} fill={t.palette.white} />
-        ) : (
-          <UnmuteIcon width={13} fill={t.palette.white} />
-        )}
-      </ControlButton>
-    </View>
-  )
-}
-
-function ControlButton({
-  onPress,
-  children,
-  label,
-  accessibilityHint,
-  style,
-}: {
-  onPress: () => void
-  children: React.ReactNode
-  label: string
-  accessibilityHint: string
-  style?: StyleProp<ViewStyle>
-}) {
-  return (
-    <View
-      style={[
-        a.absolute,
-        a.rounded_full,
-        a.justify_center,
-        {
-          backgroundColor: 'rgba(0, 0, 0, 0.5)',
-          paddingHorizontal: 4,
-          paddingVertical: 4,
-          bottom: 6,
-          minHeight: 21,
-          minWidth: 21,
-        },
-        style,
-      ]}>
-      <Pressable
-        onPress={onPress}
-        style={a.flex_1}
-        accessibilityLabel={label}
-        accessibilityHint={accessibilityHint}
-        accessibilityRole="button"
-        hitSlop={HITSLOP_30}>
-        {children}
-      </Pressable>
-    </View>
-  )
-}
diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.web.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.web.tsx
deleted file mode 100644
index 2760c7faf..000000000
--- a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.web.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export function VideoEmbedInnerNative() {
-  throw new Error('VideoEmbedInnerNative may not be used on web.')
-}
diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb.native.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb.native.tsx
deleted file mode 100644
index 8664aae14..000000000
--- a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb.native.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export function VideoEmbedInnerWeb() {
-  throw new Error('VideoEmbedInnerWeb may not be used on native.')
-}
diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb.tsx
deleted file mode 100644
index ce3a7b2c9..000000000
--- a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb.tsx
+++ /dev/null
@@ -1,307 +0,0 @@
-import {useEffect, useId, useRef, useState} from 'react'
-import {View} from 'react-native'
-import {type AppBskyEmbedVideo} from '@atproto/api'
-import {msg} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-import type * as HlsTypes from 'hls.js'
-
-import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
-import {atoms as a} from '#/alf'
-import {MediaInsetBorder} from '#/components/MediaInsetBorder'
-import * as BandwidthEstimate from './bandwidth-estimate'
-import {Controls} from './web-controls/VideoControls'
-
-export function VideoEmbedInnerWeb({
-  embed,
-  active,
-  setActive,
-  onScreen,
-  lastKnownTime,
-}: {
-  embed: AppBskyEmbedVideo.View
-  active: boolean
-  setActive: () => void
-  onScreen: boolean
-  lastKnownTime: React.MutableRefObject<number | undefined>
-}) {
-  const containerRef = useRef<HTMLDivElement>(null)
-  const videoRef = useRef<HTMLVideoElement>(null)
-  const [focused, setFocused] = useState(false)
-  const [hasSubtitleTrack, setHasSubtitleTrack] = useState(false)
-  const [hlsLoading, setHlsLoading] = useState(false)
-  const figId = useId()
-  const {_} = useLingui()
-
-  // send error up to error boundary
-  const [error, setError] = useState<Error | null>(null)
-  if (error) {
-    throw error
-  }
-
-  const hlsRef = useHLS({
-    playlist: embed.playlist,
-    setHasSubtitleTrack,
-    setError,
-    videoRef,
-    setHlsLoading,
-  })
-
-  useEffect(() => {
-    if (lastKnownTime.current && videoRef.current) {
-      videoRef.current.currentTime = lastKnownTime.current
-    }
-  }, [lastKnownTime])
-
-  return (
-    <View
-      style={[a.flex_1, a.rounded_md, a.overflow_hidden]}
-      accessibilityLabel={_(msg`Embedded video player`)}
-      accessibilityHint="">
-      <div ref={containerRef} style={{height: '100%', width: '100%'}}>
-        <figure style={{margin: 0, position: 'absolute', inset: 0}}>
-          <video
-            ref={videoRef}
-            poster={embed.thumbnail}
-            style={{width: '100%', height: '100%', objectFit: 'contain'}}
-            playsInline
-            preload="none"
-            muted={!focused}
-            aria-labelledby={embed.alt ? figId : undefined}
-            onTimeUpdate={e => {
-              lastKnownTime.current = e.currentTarget.currentTime
-            }}
-          />
-          {embed.alt && (
-            <figcaption
-              id={figId}
-              style={{
-                position: 'absolute',
-                width: 1,
-                height: 1,
-                padding: 0,
-                margin: -1,
-                overflow: 'hidden',
-                clip: 'rect(0, 0, 0, 0)',
-                whiteSpace: 'nowrap',
-                borderWidth: 0,
-              }}>
-              {embed.alt}
-            </figcaption>
-          )}
-        </figure>
-        <Controls
-          videoRef={videoRef}
-          hlsRef={hlsRef}
-          active={active}
-          setActive={setActive}
-          focused={focused}
-          setFocused={setFocused}
-          hlsLoading={hlsLoading}
-          onScreen={onScreen}
-          fullscreenRef={containerRef}
-          hasSubtitleTrack={hasSubtitleTrack}
-        />
-      </div>
-      <MediaInsetBorder />
-    </View>
-  )
-}
-
-export class HLSUnsupportedError extends Error {
-  constructor() {
-    super('HLS is not supported')
-  }
-}
-
-export class VideoNotFoundError extends Error {
-  constructor() {
-    super('Video not found')
-  }
-}
-
-type CachedPromise<T> = Promise<T> & {value: undefined | T}
-const promiseForHls = import(
-  // @ts-ignore
-  'hls.js/dist/hls.min'
-).then(mod => mod.default) as CachedPromise<typeof HlsTypes.default>
-promiseForHls.value = undefined
-promiseForHls.then(Hls => {
-  promiseForHls.value = Hls
-})
-
-function useHLS({
-  playlist,
-  setHasSubtitleTrack,
-  setError,
-  videoRef,
-  setHlsLoading,
-}: {
-  playlist: string
-  setHasSubtitleTrack: (v: boolean) => void
-  setError: (v: Error | null) => void
-  videoRef: React.RefObject<HTMLVideoElement>
-  setHlsLoading: (v: boolean) => void
-}) {
-  const [Hls, setHls] = useState<typeof HlsTypes.default | undefined>(
-    () => promiseForHls.value,
-  )
-  useEffect(() => {
-    if (!Hls) {
-      setHlsLoading(true)
-      promiseForHls.then(loadedHls => {
-        setHls(() => loadedHls)
-        setHlsLoading(false)
-      })
-    }
-  }, [Hls, setHlsLoading])
-
-  const hlsRef = useRef<HlsTypes.default | undefined>(undefined)
-  const [lowQualityFragments, setLowQualityFragments] = useState<
-    HlsTypes.Fragment[]
-  >([])
-
-  // purge low quality segments from buffer on next frag change
-  const handleFragChange = useNonReactiveCallback(
-    (
-      _event: HlsTypes.Events.FRAG_CHANGED,
-      {frag}: HlsTypes.FragChangedData,
-    ) => {
-      if (!Hls) return
-      if (!hlsRef.current) return
-      const hls = hlsRef.current
-
-      // if the current quality level goes above 0, flush the low quality segments
-      if (hls.nextAutoLevel > 0) {
-        const flushed: HlsTypes.Fragment[] = []
-
-        for (const lowQualFrag of lowQualityFragments) {
-          // avoid if close to the current fragment
-          if (Math.abs(frag.start - lowQualFrag.start) < 0.1) {
-            continue
-          }
-
-          hls.trigger(Hls.Events.BUFFER_FLUSHING, {
-            startOffset: lowQualFrag.start,
-            endOffset: lowQualFrag.end,
-            type: 'video',
-          })
-
-          flushed.push(lowQualFrag)
-        }
-
-        setLowQualityFragments(prev => prev.filter(f => !flushed.includes(f)))
-      }
-    },
-  )
-
-  const flushOnLoop = useNonReactiveCallback(() => {
-    if (!Hls) return
-    if (!hlsRef.current) return
-    const hls = hlsRef.current
-    // the above callback will catch most stale frags, but there's a corner case -
-    // if there's only one segment in the video, it won't get flushed because it avoids
-    // flushing the currently active segment. Therefore, we have to catch it when we loop
-    if (
-      hls.nextAutoLevel > 0 &&
-      lowQualityFragments.length === 1 &&
-      lowQualityFragments[0].start === 0
-    ) {
-      const lowQualFrag = lowQualityFragments[0]
-
-      hls.trigger(Hls.Events.BUFFER_FLUSHING, {
-        startOffset: lowQualFrag.start,
-        endOffset: lowQualFrag.end,
-        type: 'video',
-      })
-      setLowQualityFragments([])
-    }
-  })
-
-  useEffect(() => {
-    if (!videoRef.current) return
-    if (!Hls) return
-    if (!Hls.isSupported()) {
-      throw new HLSUnsupportedError()
-    }
-
-    const hls = new Hls({
-      maxMaxBufferLength: 10, // only load 10s ahead
-      // note: the amount buffered is affected by both maxBufferLength and maxBufferSize
-      // it will buffer until it is greater than *both* of those values
-      // so we use maxMaxBufferLength to set the actual maximum amount of buffering instead
-    })
-    hlsRef.current = hls
-
-    const latestEstimate = BandwidthEstimate.get()
-    if (latestEstimate !== undefined) {
-      hls.bandwidthEstimate = latestEstimate
-    }
-
-    hls.attachMedia(videoRef.current)
-    hls.loadSource(playlist)
-
-    // manually loop, so if we've flushed the first buffer it doesn't get confused
-    const abortController = new AbortController()
-    const {signal} = abortController
-    const videoNode = videoRef.current
-    videoNode.addEventListener(
-      'ended',
-      () => {
-        flushOnLoop()
-        videoNode.currentTime = 0
-        videoNode.play()
-      },
-      {signal},
-    )
-
-    hls.on(Hls.Events.FRAG_LOADED, () => {
-      BandwidthEstimate.set(hls.bandwidthEstimate)
-    })
-
-    hls.on(Hls.Events.SUBTITLE_TRACKS_UPDATED, (_event, data) => {
-      if (data.subtitleTracks.length > 0) {
-        setHasSubtitleTrack(true)
-      }
-    })
-
-    hls.on(Hls.Events.FRAG_BUFFERED, (_event, {frag}) => {
-      if (frag.level === 0) {
-        setLowQualityFragments(prev => [...prev, frag])
-      }
-    })
-
-    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)
-        }
-      } else {
-        console.error(data.error)
-      }
-    })
-
-    hls.on(Hls.Events.FRAG_CHANGED, handleFragChange)
-
-    return () => {
-      hlsRef.current = undefined
-      hls.detachMedia()
-      hls.destroy()
-      abortController.abort()
-    }
-  }, [
-    playlist,
-    setError,
-    setHasSubtitleTrack,
-    videoRef,
-    handleFragChange,
-    flushOnLoop,
-    Hls,
-  ])
-
-  return hlsRef
-}
diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/VideoFallback.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/VideoFallback.tsx
deleted file mode 100644
index 1b46163cc..000000000
--- a/src/view/com/util/post-embeds/VideoEmbedInner/VideoFallback.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-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>
-  )
-}
diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/bandwidth-estimate.ts b/src/view/com/util/post-embeds/VideoEmbedInner/bandwidth-estimate.ts
deleted file mode 100644
index 122e10aef..000000000
--- a/src/view/com/util/post-embeds/VideoEmbedInner/bandwidth-estimate.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-let latestBandwidthEstimate: number | undefined
-
-export function get() {
-  return latestBandwidthEstimate
-}
-
-export function set(estimate: number) {
-  if (!isNaN(estimate)) {
-    latestBandwidthEstimate = estimate
-  }
-}
diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/web-controls/ControlButton.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/web-controls/ControlButton.tsx
deleted file mode 100644
index 651046445..000000000
--- a/src/view/com/util/post-embeds/VideoEmbedInner/web-controls/ControlButton.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import React from 'react'
-import {SvgProps} from 'react-native-svg'
-
-import {atoms as a, useTheme, web} from '#/alf'
-import {PressableWithHover} from '../../../PressableWithHover'
-
-export function ControlButton({
-  active,
-  activeLabel,
-  inactiveLabel,
-  activeIcon: ActiveIcon,
-  inactiveIcon: InactiveIcon,
-  onPress,
-}: {
-  active: boolean
-  activeLabel: string
-  inactiveLabel: string
-  activeIcon: React.ComponentType<Pick<SvgProps, 'fill' | 'width'>>
-  inactiveIcon: React.ComponentType<Pick<SvgProps, 'fill' | 'width'>>
-  onPress: () => void
-}) {
-  const t = useTheme()
-  return (
-    <PressableWithHover
-      accessibilityRole="button"
-      accessibilityLabel={active ? activeLabel : inactiveLabel}
-      accessibilityHint=""
-      onPress={onPress}
-      style={[
-        a.p_xs,
-        a.rounded_full,
-        web({transition: 'background-color 0.1s'}),
-      ]}
-      hoverStyle={{backgroundColor: 'rgba(255, 255, 255, 0.2)'}}>
-      {active ? (
-        <ActiveIcon fill={t.palette.white} width={20} aria-hidden />
-      ) : (
-        <InactiveIcon fill={t.palette.white} width={20} aria-hidden />
-      )}
-    </PressableWithHover>
-  )
-}
diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/web-controls/Scrubber.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/web-controls/Scrubber.tsx
deleted file mode 100644
index 96960bad4..000000000
--- a/src/view/com/util/post-embeds/VideoEmbedInner/web-controls/Scrubber.tsx
+++ /dev/null
@@ -1,238 +0,0 @@
-import React, {useCallback, useEffect, useRef, useState} from 'react'
-import {View} from 'react-native'
-import {msg} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-
-import {isFirefox, isTouchDevice} from '#/lib/browser'
-import {clamp} from '#/lib/numbers'
-import {atoms as a, useTheme, web} from '#/alf'
-import {useInteractionState} from '#/components/hooks/useInteractionState'
-import {formatTime} from './utils'
-
-export function Scrubber({
-  duration,
-  currentTime,
-  onSeek,
-  onSeekEnd,
-  onSeekStart,
-  seekLeft,
-  seekRight,
-  togglePlayPause,
-  drawFocus,
-}: {
-  duration: number
-  currentTime: number
-  onSeek: (time: number) => void
-  onSeekEnd: () => void
-  onSeekStart: () => void
-  seekLeft: () => void
-  seekRight: () => void
-  togglePlayPause: () => void
-  drawFocus: () => void
-}) {
-  const {_} = useLingui()
-  const t = useTheme()
-  const [scrubberActive, setScrubberActive] = useState(false)
-  const {
-    state: hovered,
-    onIn: onStartHover,
-    onOut: onEndHover,
-  } = useInteractionState()
-  const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
-  const [seekPosition, setSeekPosition] = useState(0)
-  const isSeekingRef = useRef(false)
-  const barRef = useRef<HTMLDivElement>(null)
-  const circleRef = useRef<HTMLDivElement>(null)
-
-  const seek = useCallback(
-    (evt: React.PointerEvent<HTMLDivElement>) => {
-      if (!barRef.current) return
-      const {left, width} = barRef.current.getBoundingClientRect()
-      const x = evt.clientX
-      const percent = clamp((x - left) / width, 0, 1) * duration
-      onSeek(percent)
-      setSeekPosition(percent)
-    },
-    [duration, onSeek],
-  )
-
-  const onPointerDown = useCallback(
-    (evt: React.PointerEvent<HTMLDivElement>) => {
-      const target = evt.target
-      if (target instanceof Element) {
-        evt.preventDefault()
-        target.setPointerCapture(evt.pointerId)
-        isSeekingRef.current = true
-        seek(evt)
-        setScrubberActive(true)
-        onSeekStart()
-      }
-    },
-    [seek, onSeekStart],
-  )
-
-  const onPointerMove = useCallback(
-    (evt: React.PointerEvent<HTMLDivElement>) => {
-      if (isSeekingRef.current) {
-        evt.preventDefault()
-        seek(evt)
-      }
-    },
-    [seek],
-  )
-
-  const onPointerUp = useCallback(
-    (evt: React.PointerEvent<HTMLDivElement>) => {
-      const target = evt.target
-      if (isSeekingRef.current && target instanceof Element) {
-        evt.preventDefault()
-        target.releasePointerCapture(evt.pointerId)
-        isSeekingRef.current = false
-        onSeekEnd()
-        setScrubberActive(false)
-      }
-    },
-    [onSeekEnd],
-  )
-
-  useEffect(() => {
-    // HACK: there's divergent browser behaviour about what to do when
-    // a pointerUp event is fired outside the element that captured the
-    // pointer. Firefox clicks on the element the mouse is over, so we have
-    // to make everything unclickable while seeking -sfn
-    if (isFirefox && scrubberActive) {
-      document.body.classList.add('force-no-clicks')
-
-      return () => {
-        document.body.classList.remove('force-no-clicks')
-      }
-    }
-  }, [scrubberActive, onSeekEnd])
-
-  useEffect(() => {
-    if (!circleRef.current) return
-    if (focused) {
-      const abortController = new AbortController()
-      const {signal} = abortController
-      circleRef.current.addEventListener(
-        'keydown',
-        evt => {
-          // space: play/pause
-          // arrow left: seek backward
-          // arrow right: seek forward
-
-          if (evt.key === ' ') {
-            evt.preventDefault()
-            drawFocus()
-            togglePlayPause()
-          } else if (evt.key === 'ArrowLeft') {
-            evt.preventDefault()
-            drawFocus()
-            seekLeft()
-          } else if (evt.key === 'ArrowRight') {
-            evt.preventDefault()
-            drawFocus()
-            seekRight()
-          }
-        },
-        {signal},
-      )
-
-      return () => abortController.abort()
-    }
-  }, [focused, seekLeft, seekRight, togglePlayPause, drawFocus])
-
-  const progress = scrubberActive ? seekPosition : currentTime
-  const progressPercent = (progress / duration) * 100
-
-  return (
-    <View
-      testID="scrubber"
-      style={[
-        {height: isTouchDevice ? 32 : 18, width: '100%'},
-        a.flex_shrink_0,
-        a.px_xs,
-      ]}
-      onPointerEnter={onStartHover}
-      onPointerLeave={onEndHover}>
-      <div
-        ref={barRef}
-        style={{
-          flex: 1,
-          display: 'flex',
-          alignItems: 'center',
-          position: 'relative',
-          cursor: scrubberActive ? 'grabbing' : 'grab',
-          padding: '4px 0',
-        }}
-        onPointerDown={onPointerDown}
-        onPointerMove={onPointerMove}
-        onPointerUp={onPointerUp}
-        onPointerCancel={onPointerUp}>
-        <View
-          style={[
-            a.w_full,
-            a.rounded_full,
-            a.overflow_hidden,
-            {backgroundColor: 'rgba(255, 255, 255, 0.4)'},
-            {height: hovered || scrubberActive ? 6 : 3},
-            web({transition: 'height 0.1s ease'}),
-          ]}>
-          {duration > 0 && (
-            <View
-              style={[
-                a.h_full,
-                {backgroundColor: t.palette.white},
-                {width: `${progressPercent}%`},
-              ]}
-            />
-          )}
-        </View>
-        <div
-          ref={circleRef}
-          aria-label={_(
-            msg`Seek slider. Use the arrow keys to seek forwards and backwards, and space to play/pause`,
-          )}
-          role="slider"
-          aria-valuemax={duration}
-          aria-valuemin={0}
-          aria-valuenow={currentTime}
-          aria-valuetext={_(
-            msg`${formatTime(currentTime)} of ${formatTime(duration)}`,
-          )}
-          tabIndex={0}
-          onFocus={onFocus}
-          onBlur={onBlur}
-          style={{
-            position: 'absolute',
-            height: 16,
-            width: 16,
-            left: `calc(${progressPercent}% - 8px)`,
-            borderRadius: 8,
-            pointerEvents: 'none',
-          }}>
-          <View
-            style={[
-              a.w_full,
-              a.h_full,
-              a.rounded_full,
-              {backgroundColor: t.palette.white},
-              {
-                transform: [
-                  {
-                    scale:
-                      hovered || scrubberActive || focused
-                        ? scrubberActive
-                          ? 1
-                          : 0.6
-                        : 0,
-                  },
-                ],
-              },
-            ]}
-          />
-        </div>
-      </div>
-    </View>
-  )
-}
diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/web-controls/VideoControls.native.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/web-controls/VideoControls.native.tsx
deleted file mode 100644
index e2e24ed36..000000000
--- a/src/view/com/util/post-embeds/VideoEmbedInner/web-controls/VideoControls.native.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export function Controls() {
-  throw new Error('VideoWebControls may not be used on native.')
-}
diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/web-controls/VideoControls.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/web-controls/VideoControls.tsx
deleted file mode 100644
index 6d14deafc..000000000
--- a/src/view/com/util/post-embeds/VideoEmbedInner/web-controls/VideoControls.tsx
+++ /dev/null
@@ -1,427 +0,0 @@
-import {useCallback, useEffect, useRef, useState} from 'react'
-import {Pressable, View} from 'react-native'
-import {msg, Trans} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-import type Hls from 'hls.js'
-
-import {isTouchDevice} from '#/lib/browser'
-import {clamp} from '#/lib/numbers'
-import {isIPhoneWeb} from '#/platform/detection'
-import {
-  useAutoplayDisabled,
-  useSetSubtitlesEnabled,
-  useSubtitlesEnabled,
-} from '#/state/preferences'
-import {atoms as a, useTheme, web} from '#/alf'
-import {useIsWithinMessage} from '#/components/dms/MessageContext'
-import {useFullscreen} from '#/components/hooks/useFullscreen'
-import {useInteractionState} from '#/components/hooks/useInteractionState'
-import {
-  ArrowsDiagonalIn_Stroke2_Corner0_Rounded as ArrowsInIcon,
-  ArrowsDiagonalOut_Stroke2_Corner0_Rounded as ArrowsOutIcon,
-} from '#/components/icons/ArrowsDiagonal'
-import {
-  CC_Filled_Corner0_Rounded as CCActiveIcon,
-  CC_Stroke2_Corner0_Rounded as CCInactiveIcon,
-} from '#/components/icons/CC'
-import {Pause_Filled_Corner0_Rounded as PauseIcon} from '#/components/icons/Pause'
-import {Play_Filled_Corner0_Rounded as PlayIcon} from '#/components/icons/Play'
-import {Loader} from '#/components/Loader'
-import {Text} from '#/components/Typography'
-import {TimeIndicator} from '../TimeIndicator'
-import {ControlButton} from './ControlButton'
-import {Scrubber} from './Scrubber'
-import {formatTime, useVideoElement} from './utils'
-import {VolumeControl} from './VolumeControl'
-
-export function Controls({
-  videoRef,
-  hlsRef,
-  active,
-  setActive,
-  focused,
-  setFocused,
-  onScreen,
-  fullscreenRef,
-  hlsLoading,
-  hasSubtitleTrack,
-}: {
-  videoRef: React.RefObject<HTMLVideoElement>
-  hlsRef: React.RefObject<Hls | undefined>
-  active: boolean
-  setActive: () => void
-  focused: boolean
-  setFocused: (focused: boolean) => void
-  onScreen: boolean
-  fullscreenRef: React.RefObject<HTMLDivElement>
-  hlsLoading: boolean
-  hasSubtitleTrack: boolean
-}) {
-  const {
-    play,
-    pause,
-    playing,
-    muted,
-    changeMuted,
-    togglePlayPause,
-    currentTime,
-    duration,
-    buffering,
-    error,
-    canPlay,
-  } = useVideoElement(videoRef)
-  const t = useTheme()
-  const {_} = useLingui()
-  const subtitlesEnabled = useSubtitlesEnabled()
-  const setSubtitlesEnabled = useSetSubtitlesEnabled()
-  const {
-    state: hovered,
-    onIn: onHover,
-    onOut: onEndHover,
-  } = useInteractionState()
-  const [isFullscreen, toggleFullscreen] = useFullscreen(fullscreenRef)
-  const {state: hasFocus, onIn: onFocus, onOut: onBlur} = useInteractionState()
-  const [interactingViaKeypress, setInteractingViaKeypress] = useState(false)
-  const showSpinner = hlsLoading || buffering
-  const {
-    state: volumeHovered,
-    onIn: onVolumeHover,
-    onOut: onVolumeEndHover,
-  } = useInteractionState()
-
-  const onKeyDown = useCallback(() => {
-    setInteractingViaKeypress(true)
-  }, [])
-
-  useEffect(() => {
-    if (interactingViaKeypress) {
-      document.addEventListener('click', () => setInteractingViaKeypress(false))
-      return () => {
-        document.removeEventListener('click', () =>
-          setInteractingViaKeypress(false),
-        )
-      }
-    }
-  }, [interactingViaKeypress])
-
-  useEffect(() => {
-    if (isFullscreen) {
-      document.documentElement.style.scrollbarGutter = 'unset'
-      return () => {
-        document.documentElement.style.removeProperty('scrollbar-gutter')
-      }
-    }
-  }, [isFullscreen])
-
-  // pause + unfocus when another video is active
-  useEffect(() => {
-    if (!active) {
-      pause()
-      setFocused(false)
-    }
-  }, [active, pause, setFocused])
-
-  // autoplay/pause based on visibility
-  const isWithinMessage = useIsWithinMessage()
-  const autoplayDisabled = useAutoplayDisabled() || isWithinMessage
-  useEffect(() => {
-    if (active) {
-      if (onScreen) {
-        if (!autoplayDisabled) play()
-      } else {
-        pause()
-      }
-    }
-  }, [onScreen, pause, active, play, autoplayDisabled])
-
-  // use minimal quality when not focused
-  useEffect(() => {
-    if (!hlsRef.current) return
-    if (focused) {
-      // allow 30s of buffering
-      hlsRef.current.config.maxMaxBufferLength = 30
-    } else {
-      // back to what we initially set
-      hlsRef.current.config.maxMaxBufferLength = 10
-    }
-  }, [hlsRef, focused])
-
-  useEffect(() => {
-    if (!hlsRef.current) return
-    if (hasSubtitleTrack && subtitlesEnabled && canPlay) {
-      hlsRef.current.subtitleTrack = 0
-    } else {
-      hlsRef.current.subtitleTrack = -1
-    }
-  }, [hasSubtitleTrack, subtitlesEnabled, hlsRef, canPlay])
-
-  // clicking on any button should focus the player, if it's not already focused
-  const drawFocus = useCallback(() => {
-    if (!active) {
-      setActive()
-    }
-    setFocused(true)
-  }, [active, setActive, setFocused])
-
-  const onPressEmptySpace = useCallback(() => {
-    if (!focused) {
-      drawFocus()
-      if (autoplayDisabled) play()
-    } else {
-      togglePlayPause()
-    }
-  }, [togglePlayPause, drawFocus, focused, autoplayDisabled, play])
-
-  const onPressPlayPause = useCallback(() => {
-    drawFocus()
-    togglePlayPause()
-  }, [drawFocus, togglePlayPause])
-
-  const onPressSubtitles = useCallback(() => {
-    drawFocus()
-    setSubtitlesEnabled(!subtitlesEnabled)
-  }, [drawFocus, setSubtitlesEnabled, subtitlesEnabled])
-
-  const onPressFullscreen = useCallback(() => {
-    drawFocus()
-    toggleFullscreen()
-  }, [drawFocus, toggleFullscreen])
-
-  const onSeek = useCallback(
-    (time: number) => {
-      if (!videoRef.current) return
-      if (videoRef.current.fastSeek) {
-        videoRef.current.fastSeek(time)
-      } else {
-        videoRef.current.currentTime = time
-      }
-    },
-    [videoRef],
-  )
-
-  const playStateBeforeSeekRef = useRef(false)
-
-  const onSeekStart = useCallback(() => {
-    drawFocus()
-    playStateBeforeSeekRef.current = playing
-    pause()
-  }, [playing, pause, drawFocus])
-
-  const onSeekEnd = useCallback(() => {
-    if (playStateBeforeSeekRef.current) {
-      play()
-    }
-  }, [play])
-
-  const seekLeft = useCallback(() => {
-    if (!videoRef.current) return
-    // eslint-disable-next-line @typescript-eslint/no-shadow
-    const currentTime = videoRef.current.currentTime
-    // eslint-disable-next-line @typescript-eslint/no-shadow
-    const duration = videoRef.current.duration || 0
-    onSeek(clamp(currentTime - 5, 0, duration))
-  }, [onSeek, videoRef])
-
-  const seekRight = useCallback(() => {
-    if (!videoRef.current) return
-    // eslint-disable-next-line @typescript-eslint/no-shadow
-    const currentTime = videoRef.current.currentTime
-    // eslint-disable-next-line @typescript-eslint/no-shadow
-    const duration = videoRef.current.duration || 0
-    onSeek(clamp(currentTime + 5, 0, duration))
-  }, [onSeek, videoRef])
-
-  const [showCursor, setShowCursor] = useState(true)
-  const cursorTimeoutRef = useRef<ReturnType<typeof setTimeout>>()
-  const onPointerMoveEmptySpace = useCallback(() => {
-    setShowCursor(true)
-    if (cursorTimeoutRef.current) {
-      clearTimeout(cursorTimeoutRef.current)
-    }
-    cursorTimeoutRef.current = setTimeout(() => {
-      setShowCursor(false)
-      onEndHover()
-    }, 2000)
-  }, [onEndHover])
-  const onPointerLeaveEmptySpace = useCallback(() => {
-    setShowCursor(false)
-    if (cursorTimeoutRef.current) {
-      clearTimeout(cursorTimeoutRef.current)
-    }
-  }, [])
-
-  // these are used to trigger the hover state. on mobile, the hover state
-  // should stick around for a bit after they tap, and if the controls aren't
-  // present this initial tab should *only* show the controls and not activate anything
-
-  const onPointerDown = useCallback(
-    (evt: React.PointerEvent<HTMLDivElement>) => {
-      if (evt.pointerType !== 'mouse' && !hovered) {
-        evt.preventDefault()
-      }
-      clearTimeout(timeoutRef.current)
-    },
-    [hovered],
-  )
-
-  const timeoutRef = useRef<ReturnType<typeof setTimeout>>()
-
-  const onHoverWithTimeout = useCallback(() => {
-    onHover()
-    clearTimeout(timeoutRef.current)
-  }, [onHover])
-
-  const onEndHoverWithTimeout = useCallback(
-    (evt: React.PointerEvent<HTMLDivElement>) => {
-      // if touch, end after 3s
-      // if mouse, end immediately
-      if (evt.pointerType !== 'mouse') {
-        setTimeout(onEndHover, 3000)
-      } else {
-        onEndHover()
-      }
-    },
-    [onEndHover],
-  )
-
-  const showControls =
-    ((focused || autoplayDisabled) && !playing) ||
-    (interactingViaKeypress ? hasFocus : hovered)
-
-  return (
-    <div
-      style={{
-        position: 'absolute',
-        inset: 0,
-        overflow: 'hidden',
-        display: 'flex',
-        flexDirection: 'column',
-      }}
-      onClick={evt => {
-        evt.stopPropagation()
-        setInteractingViaKeypress(false)
-      }}
-      onPointerEnter={onHoverWithTimeout}
-      onPointerMove={onHoverWithTimeout}
-      onPointerLeave={onEndHoverWithTimeout}
-      onPointerDown={onPointerDown}
-      onFocus={onFocus}
-      onBlur={onBlur}
-      onKeyDown={onKeyDown}>
-      <Pressable
-        accessibilityRole="button"
-        onPointerEnter={onPointerMoveEmptySpace}
-        onPointerMove={onPointerMoveEmptySpace}
-        onPointerLeave={onPointerLeaveEmptySpace}
-        accessibilityLabel={_(
-          !focused
-            ? msg`Unmute video`
-            : playing
-            ? msg`Pause video`
-            : msg`Play video`,
-        )}
-        accessibilityHint=""
-        style={[
-          a.flex_1,
-          web({cursor: showCursor || !playing ? 'pointer' : 'none'}),
-        ]}
-        onPress={onPressEmptySpace}
-      />
-      {!showControls && !focused && duration > 0 && (
-        <TimeIndicator time={Math.floor(duration - currentTime)} />
-      )}
-      <View
-        style={[
-          a.flex_shrink_0,
-          a.w_full,
-          a.px_xs,
-          web({
-            background:
-              'linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.7))',
-          }),
-          {opacity: showControls ? 1 : 0},
-          {transition: 'opacity 0.2s ease-in-out'},
-        ]}>
-        {(!volumeHovered || isTouchDevice) && (
-          <Scrubber
-            duration={duration}
-            currentTime={currentTime}
-            onSeek={onSeek}
-            onSeekStart={onSeekStart}
-            onSeekEnd={onSeekEnd}
-            seekLeft={seekLeft}
-            seekRight={seekRight}
-            togglePlayPause={togglePlayPause}
-            drawFocus={drawFocus}
-          />
-        )}
-        <View
-          style={[
-            a.flex_1,
-            a.px_xs,
-            a.pb_sm,
-            a.gap_sm,
-            a.flex_row,
-            a.align_center,
-          ]}>
-          <ControlButton
-            active={playing}
-            activeLabel={_(msg`Pause`)}
-            inactiveLabel={_(msg`Play`)}
-            activeIcon={PauseIcon}
-            inactiveIcon={PlayIcon}
-            onPress={onPressPlayPause}
-          />
-          <View style={a.flex_1} />
-          <Text
-            style={[
-              a.px_xs,
-              {color: t.palette.white, fontVariant: ['tabular-nums']},
-            ]}>
-            {formatTime(currentTime)} / {formatTime(duration)}
-          </Text>
-          {hasSubtitleTrack && (
-            <ControlButton
-              active={subtitlesEnabled}
-              activeLabel={_(msg`Disable subtitles`)}
-              inactiveLabel={_(msg`Enable subtitles`)}
-              activeIcon={CCActiveIcon}
-              inactiveIcon={CCInactiveIcon}
-              onPress={onPressSubtitles}
-            />
-          )}
-          <VolumeControl
-            muted={muted}
-            changeMuted={changeMuted}
-            hovered={volumeHovered}
-            onHover={onVolumeHover}
-            onEndHover={onVolumeEndHover}
-            drawFocus={drawFocus}
-          />
-          {!isIPhoneWeb && (
-            <ControlButton
-              active={isFullscreen}
-              activeLabel={_(msg`Exit fullscreen`)}
-              inactiveLabel={_(msg`Enter fullscreen`)}
-              activeIcon={ArrowsInIcon}
-              inactiveIcon={ArrowsOutIcon}
-              onPress={onPressFullscreen}
-            />
-          )}
-        </View>
-      </View>
-      {(showSpinner || error) && (
-        <View
-          pointerEvents="none"
-          style={[a.absolute, a.inset_0, a.justify_center, a.align_center]}>
-          {showSpinner && <Loader fill={t.palette.white} size="lg" />}
-          {error && (
-            <Text style={{color: t.palette.white}}>
-              <Trans>An error occurred</Trans>
-            </Text>
-          )}
-        </View>
-      )}
-    </div>
-  )
-}
diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/web-controls/VolumeControl.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/web-controls/VolumeControl.tsx
deleted file mode 100644
index 90ffb9e6b..000000000
--- a/src/view/com/util/post-embeds/VideoEmbedInner/web-controls/VolumeControl.tsx
+++ /dev/null
@@ -1,110 +0,0 @@
-import React, {useCallback} from 'react'
-import {View} from 'react-native'
-import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'
-import {msg} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-
-import {isSafari, isTouchDevice} from '#/lib/browser'
-import {atoms as a} from '#/alf'
-import {Mute_Stroke2_Corner0_Rounded as MuteIcon} from '#/components/icons/Mute'
-import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as UnmuteIcon} from '#/components/icons/Speaker'
-import {useVideoVolumeState} from '../../VideoVolumeContext'
-import {ControlButton} from './ControlButton'
-
-export function VolumeControl({
-  muted,
-  changeMuted,
-  hovered,
-  onHover,
-  onEndHover,
-  drawFocus,
-}: {
-  muted: boolean
-  changeMuted: (muted: boolean | ((prev: boolean) => boolean)) => void
-  hovered: boolean
-  onHover: () => void
-  onEndHover: () => void
-  drawFocus: () => void
-}) {
-  const {_} = useLingui()
-  const [volume, setVolume] = useVideoVolumeState()
-
-  const onVolumeChange = useCallback(
-    (evt: React.ChangeEvent<HTMLInputElement>) => {
-      drawFocus()
-      const vol = sliderVolumeToVideoVolume(Number(evt.target.value))
-      setVolume(vol)
-      changeMuted(vol === 0)
-    },
-    [setVolume, drawFocus, changeMuted],
-  )
-
-  const sliderVolume = muted ? 0 : videoVolumeToSliderVolume(volume)
-
-  const isZeroVolume = volume === 0
-  const onPressMute = useCallback(() => {
-    drawFocus()
-    if (isZeroVolume) {
-      setVolume(1)
-      changeMuted(false)
-    } else {
-      changeMuted(prevMuted => !prevMuted)
-    }
-  }, [drawFocus, setVolume, isZeroVolume, changeMuted])
-
-  return (
-    <View
-      onPointerEnter={onHover}
-      onPointerLeave={onEndHover}
-      style={[a.relative]}>
-      {hovered && !isTouchDevice && (
-        <Animated.View
-          entering={FadeIn.duration(100)}
-          exiting={FadeOut.duration(100)}
-          style={[a.absolute, a.w_full, {height: 100, bottom: '100%'}]}>
-          <View
-            style={[
-              a.flex_1,
-              a.mb_xs,
-              a.px_2xs,
-              a.py_xs,
-              {backgroundColor: 'rgba(0, 0, 0, 0.6)'},
-              a.rounded_xs,
-              a.align_center,
-            ]}>
-            <input
-              type="range"
-              min={0}
-              max={100}
-              value={sliderVolume}
-              aria-label={_(msg`Volume`)}
-              style={
-                // Ridiculous safari hack for old version of safari. Fixed in sonoma beta -h
-                isSafari ? {height: 92, minHeight: '100%'} : {height: '100%'}
-              }
-              onChange={onVolumeChange}
-              // @ts-expect-error for old versions of firefox, and then re-using it for targeting the CSS -sfn
-              orient="vertical"
-            />
-          </View>
-        </Animated.View>
-      )}
-      <ControlButton
-        active={muted || volume === 0}
-        activeLabel={_(msg({message: `Unmute`, context: 'video'}))}
-        inactiveLabel={_(msg({message: `Mute`, context: 'video'}))}
-        activeIcon={MuteIcon}
-        inactiveIcon={UnmuteIcon}
-        onPress={onPressMute}
-      />
-    </View>
-  )
-}
-
-function sliderVolumeToVideoVolume(value: number) {
-  return Math.pow(value / 100, 4)
-}
-
-function videoVolumeToSliderVolume(value: number) {
-  return Math.round(Math.pow(value, 1 / 4) * 100)
-}
diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/web-controls/utils.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/web-controls/utils.tsx
deleted file mode 100644
index 108814ea2..000000000
--- a/src/view/com/util/post-embeds/VideoEmbedInner/web-controls/utils.tsx
+++ /dev/null
@@ -1,240 +0,0 @@
-import React, {useCallback, useEffect, useRef, useState} from 'react'
-
-import {isSafari} from '#/lib/browser'
-import {useVideoVolumeState} from '../../VideoVolumeContext'
-
-export function useVideoElement(ref: React.RefObject<HTMLVideoElement>) {
-  const [playing, setPlaying] = useState(false)
-  const [muted, setMuted] = useState(true)
-  const [currentTime, setCurrentTime] = useState(0)
-  const [volume, setVolume] = useVideoVolumeState()
-  const [duration, setDuration] = useState(0)
-  const [buffering, setBuffering] = useState(false)
-  const [error, setError] = useState(false)
-  const [canPlay, setCanPlay] = useState(false)
-  const playWhenReadyRef = useRef(false)
-
-  useEffect(() => {
-    if (!ref.current) return
-    ref.current.volume = volume
-  }, [ref, volume])
-
-  useEffect(() => {
-    if (!ref.current) return
-
-    let bufferingTimeout: ReturnType<typeof setTimeout> | undefined
-
-    function round(num: number) {
-      return Math.round(num * 100) / 100
-    }
-
-    // Initial values
-    setCurrentTime(round(ref.current.currentTime) || 0)
-    setDuration(round(ref.current.duration) || 0)
-    setMuted(ref.current.muted)
-    setPlaying(!ref.current.paused)
-    setVolume(ref.current.volume)
-
-    const handleTimeUpdate = () => {
-      if (!ref.current) return
-      setCurrentTime(round(ref.current.currentTime) || 0)
-      // HACK: Safari randomly fires `stalled` events when changing between segments
-      // let's just clear the buffering state if the video is still progressing -sfn
-      if (isSafari) {
-        if (bufferingTimeout) clearTimeout(bufferingTimeout)
-        setBuffering(false)
-      }
-    }
-
-    const handleDurationChange = () => {
-      if (!ref.current) return
-      setDuration(round(ref.current.duration) || 0)
-    }
-
-    const handlePlay = () => {
-      setPlaying(true)
-    }
-
-    const handlePause = () => {
-      setPlaying(false)
-    }
-
-    const handleVolumeChange = () => {
-      if (!ref.current) return
-      setMuted(ref.current.muted)
-    }
-
-    const handleError = () => {
-      setError(true)
-    }
-
-    const handleCanPlay = async () => {
-      if (bufferingTimeout) clearTimeout(bufferingTimeout)
-      setBuffering(false)
-      setCanPlay(true)
-
-      if (!ref.current) return
-      if (playWhenReadyRef.current) {
-        try {
-          await ref.current.play()
-        } catch (e: any) {
-          if (
-            !e.message?.includes(`The request is not allowed by the user agent`)
-          ) {
-            throw e
-          }
-        }
-        playWhenReadyRef.current = false
-      }
-    }
-
-    const handleCanPlayThrough = () => {
-      if (bufferingTimeout) clearTimeout(bufferingTimeout)
-      setBuffering(false)
-    }
-
-    const handleWaiting = () => {
-      if (bufferingTimeout) clearTimeout(bufferingTimeout)
-      bufferingTimeout = setTimeout(() => {
-        setBuffering(true)
-      }, 500) // Delay to avoid frequent buffering state changes
-    }
-
-    const handlePlaying = () => {
-      if (bufferingTimeout) clearTimeout(bufferingTimeout)
-      setBuffering(false)
-      setError(false)
-    }
-
-    const handleStalled = () => {
-      if (bufferingTimeout) clearTimeout(bufferingTimeout)
-      bufferingTimeout = setTimeout(() => {
-        setBuffering(true)
-      }, 500) // Delay to avoid frequent buffering state changes
-    }
-
-    const handleEnded = () => {
-      setPlaying(false)
-      setBuffering(false)
-      setError(false)
-    }
-
-    const abortController = new AbortController()
-
-    ref.current.addEventListener('timeupdate', handleTimeUpdate, {
-      signal: abortController.signal,
-    })
-    ref.current.addEventListener('durationchange', handleDurationChange, {
-      signal: abortController.signal,
-    })
-    ref.current.addEventListener('play', handlePlay, {
-      signal: abortController.signal,
-    })
-    ref.current.addEventListener('pause', handlePause, {
-      signal: abortController.signal,
-    })
-    ref.current.addEventListener('volumechange', handleVolumeChange, {
-      signal: abortController.signal,
-    })
-    ref.current.addEventListener('error', handleError, {
-      signal: abortController.signal,
-    })
-    ref.current.addEventListener('canplay', handleCanPlay, {
-      signal: abortController.signal,
-    })
-    ref.current.addEventListener('canplaythrough', handleCanPlayThrough, {
-      signal: abortController.signal,
-    })
-    ref.current.addEventListener('waiting', handleWaiting, {
-      signal: abortController.signal,
-    })
-    ref.current.addEventListener('playing', handlePlaying, {
-      signal: abortController.signal,
-    })
-    ref.current.addEventListener('stalled', handleStalled, {
-      signal: abortController.signal,
-    })
-    ref.current.addEventListener('ended', handleEnded, {
-      signal: abortController.signal,
-    })
-
-    return () => {
-      abortController.abort()
-      clearTimeout(bufferingTimeout)
-    }
-  }, [ref, setVolume])
-
-  const play = useCallback(() => {
-    if (!ref.current) return
-
-    if (ref.current.ended) {
-      ref.current.currentTime = 0
-    }
-
-    if (ref.current.readyState < HTMLMediaElement.HAVE_FUTURE_DATA) {
-      playWhenReadyRef.current = true
-    } else {
-      const promise = ref.current.play()
-      if (promise !== undefined) {
-        promise.catch(err => {
-          console.error('Error playing video:', err)
-        })
-      }
-    }
-  }, [ref])
-
-  const pause = useCallback(() => {
-    if (!ref.current) return
-
-    ref.current.pause()
-    playWhenReadyRef.current = false
-  }, [ref])
-
-  const togglePlayPause = useCallback(() => {
-    if (!ref.current) return
-
-    if (ref.current.paused) {
-      play()
-    } else {
-      pause()
-    }
-  }, [ref, play, pause])
-
-  const changeMuted = useCallback(
-    (newMuted: boolean | ((prev: boolean) => boolean)) => {
-      if (!ref.current) return
-
-      const value =
-        typeof newMuted === 'function' ? newMuted(ref.current.muted) : newMuted
-      ref.current.muted = value
-    },
-    [ref],
-  )
-
-  return {
-    play,
-    pause,
-    togglePlayPause,
-    duration,
-    currentTime,
-    playing,
-    muted,
-    changeMuted,
-    buffering,
-    error,
-    canPlay,
-  }
-}
-
-export function formatTime(time: number) {
-  if (isNaN(time)) {
-    return '--'
-  }
-
-  time = Math.round(time)
-
-  const minutes = Math.floor(time / 60)
-  const seconds = String(time % 60).padStart(2, '0')
-
-  return `${minutes}:${seconds}`
-}
diff --git a/src/view/com/util/post-embeds/VideoVolumeContext.tsx b/src/view/com/util/post-embeds/VideoVolumeContext.tsx
deleted file mode 100644
index 6343081da..000000000
--- a/src/view/com/util/post-embeds/VideoVolumeContext.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import React from 'react'
-
-const Context = React.createContext<{
-  // native
-  muted: boolean
-  setMuted: React.Dispatch<React.SetStateAction<boolean>>
-  // web
-  volume: number
-  setVolume: React.Dispatch<React.SetStateAction<number>>
-} | null>(null)
-
-export function Provider({children}: {children: React.ReactNode}) {
-  const [muted, setMuted] = React.useState(true)
-  const [volume, setVolume] = React.useState(1)
-
-  const value = React.useMemo(
-    () => ({
-      muted,
-      setMuted,
-      volume,
-      setVolume,
-    }),
-    [muted, setMuted, volume, setVolume],
-  )
-
-  return <Context.Provider value={value}>{children}</Context.Provider>
-}
-
-export function useVideoVolumeState() {
-  const context = React.useContext(Context)
-  if (!context) {
-    throw new Error(
-      'useVideoVolumeState must be used within a VideoVolumeProvider',
-    )
-  }
-  return [context.volume, context.setVolume] as const
-}
-
-export function useVideoMuteState() {
-  const context = React.useContext(Context)
-  if (!context) {
-    throw new Error(
-      'useVideoMuteState must be used within a VideoVolumeProvider',
-    )
-  }
-  return [context.muted, context.setMuted] as const
-}
diff --git a/src/view/com/util/post-embeds/index.tsx b/src/view/com/util/post-embeds/index.tsx
deleted file mode 100644
index 4cf71f948..000000000
--- a/src/view/com/util/post-embeds/index.tsx
+++ /dev/null
@@ -1,327 +0,0 @@
-import React from 'react'
-import {
-  InteractionManager,
-  type StyleProp,
-  StyleSheet,
-  View,
-  type ViewStyle,
-} from 'react-native'
-import {
-  type AnimatedRef,
-  measure,
-  type MeasuredDimensions,
-  runOnJS,
-  runOnUI,
-} from 'react-native-reanimated'
-import {Image} from 'expo-image'
-import {
-  AppBskyEmbedExternal,
-  AppBskyEmbedImages,
-  AppBskyEmbedRecord,
-  AppBskyEmbedRecordWithMedia,
-  AppBskyEmbedVideo,
-  AppBskyFeedDefs,
-  AppBskyGraphDefs,
-  moderateFeedGenerator,
-  moderateUserList,
-  type ModerationDecision,
-} from '@atproto/api'
-
-import {usePalette} from '#/lib/hooks/usePalette'
-import {useLightboxControls} from '#/state/lightbox'
-import {useModerationOpts} from '#/state/preferences/moderation-opts'
-import {FeedSourceCard} from '#/view/com/feeds/FeedSourceCard'
-import {atoms as a, useTheme} from '#/alf'
-import * as ListCard from '#/components/ListCard'
-import {Embed as StarterPackCard} from '#/components/StarterPack/StarterPackCard'
-import {ContentHider} from '../../../../components/moderation/ContentHider'
-import {type Dimensions} from '../../lightbox/ImageViewing/@types'
-import {AutoSizedImage} from '../images/AutoSizedImage'
-import {ImageLayoutGrid} from '../images/ImageLayoutGrid'
-import {ExternalLinkEmbed} from './ExternalLinkEmbed'
-import {MaybeQuoteEmbed} from './QuoteEmbed'
-import {PostEmbedViewContext, QuoteEmbedViewContext} from './types'
-import {VideoEmbed} from './VideoEmbed'
-
-export * from './types'
-
-type Embed =
-  | AppBskyEmbedRecord.View
-  | AppBskyEmbedImages.View
-  | AppBskyEmbedVideo.View
-  | AppBskyEmbedExternal.View
-  | AppBskyEmbedRecordWithMedia.View
-  | {$type: string; [k: string]: unknown}
-
-export function PostEmbeds({
-  embed,
-  moderation,
-  onOpen,
-  style,
-  allowNestedQuotes,
-  viewContext,
-}: {
-  embed?: Embed
-  moderation?: ModerationDecision
-  onOpen?: () => void
-  style?: StyleProp<ViewStyle>
-  allowNestedQuotes?: boolean
-  viewContext?: PostEmbedViewContext
-}) {
-  const {openLightbox} = useLightboxControls()
-
-  // quote post with media
-  // =
-  if (AppBskyEmbedRecordWithMedia.isView(embed)) {
-    return (
-      <View style={style}>
-        <PostEmbeds
-          embed={embed.media}
-          moderation={moderation}
-          onOpen={onOpen}
-          viewContext={viewContext}
-        />
-        <MaybeQuoteEmbed
-          embed={embed.record}
-          onOpen={onOpen}
-          viewContext={
-            viewContext === PostEmbedViewContext.Feed
-              ? QuoteEmbedViewContext.FeedEmbedRecordWithMedia
-              : undefined
-          }
-        />
-      </View>
-    )
-  }
-
-  if (AppBskyEmbedRecord.isView(embed)) {
-    // custom feed embed (i.e. generator view)
-    if (AppBskyFeedDefs.isGeneratorView(embed.record)) {
-      return (
-        <View style={a.mt_sm}>
-          <MaybeFeedCard view={embed.record} />
-        </View>
-      )
-    }
-
-    // list embed
-    if (AppBskyGraphDefs.isListView(embed.record)) {
-      return (
-        <View style={a.mt_sm}>
-          <MaybeListCard view={embed.record} />
-        </View>
-      )
-    }
-
-    // starter pack embed
-    if (AppBskyGraphDefs.isStarterPackViewBasic(embed.record)) {
-      return (
-        <View style={a.mt_sm}>
-          <StarterPackCard starterPack={embed.record} />
-        </View>
-      )
-    }
-
-    // quote post
-    // =
-    return (
-      <MaybeQuoteEmbed
-        embed={embed}
-        style={style}
-        onOpen={onOpen}
-        allowNestedQuotes={allowNestedQuotes}
-      />
-    )
-  }
-
-  // image embed
-  // =
-  if (AppBskyEmbedImages.isView(embed)) {
-    const {images} = embed
-
-    if (images.length > 0) {
-      const items = embed.images.map(img => ({
-        uri: img.fullsize,
-        thumbUri: img.thumb,
-        alt: img.alt,
-        dimensions: img.aspectRatio ?? null,
-      }))
-      const _openLightbox = (
-        index: number,
-        thumbRects: (MeasuredDimensions | null)[],
-        fetchedDims: (Dimensions | null)[],
-      ) => {
-        openLightbox({
-          images: items.map((item, i) => ({
-            ...item,
-            thumbRect: thumbRects[i] ?? null,
-            thumbDimensions: fetchedDims[i] ?? null,
-            type: 'image',
-          })),
-          index,
-        })
-      }
-      const onPress = (
-        index: number,
-        refs: AnimatedRef<any>[],
-        fetchedDims: (Dimensions | null)[],
-      ) => {
-        runOnUI(() => {
-          'worklet'
-          const rects: (MeasuredDimensions | null)[] = []
-          for (const r of refs) {
-            rects.push(measure(r))
-          }
-          runOnJS(_openLightbox)(index, rects, fetchedDims)
-        })()
-      }
-      const onPressIn = (_: number) => {
-        InteractionManager.runAfterInteractions(() => {
-          Image.prefetch(items.map(i => i.uri))
-        })
-      }
-
-      if (images.length === 1) {
-        const image = images[0]
-        return (
-          <ContentHider modui={moderation?.ui('contentMedia')}>
-            <View style={[a.mt_sm, style]}>
-              <AutoSizedImage
-                crop={
-                  viewContext === PostEmbedViewContext.ThreadHighlighted
-                    ? 'none'
-                    : viewContext ===
-                      PostEmbedViewContext.FeedEmbedRecordWithMedia
-                    ? 'square'
-                    : 'constrained'
-                }
-                image={image}
-                onPress={(containerRef, dims) =>
-                  onPress(0, [containerRef], [dims])
-                }
-                onPressIn={() => onPressIn(0)}
-                hideBadge={
-                  viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia
-                }
-              />
-            </View>
-          </ContentHider>
-        )
-      }
-
-      return (
-        <ContentHider modui={moderation?.ui('contentMedia')}>
-          <View style={[a.mt_sm, style]}>
-            <ImageLayoutGrid
-              images={embed.images}
-              onPress={onPress}
-              onPressIn={onPressIn}
-              viewContext={viewContext}
-            />
-          </View>
-        </ContentHider>
-      )
-    }
-  }
-
-  // external link embed
-  // =
-  if (AppBskyEmbedExternal.isView(embed)) {
-    const link = embed.external
-    return (
-      <ContentHider modui={moderation?.ui('contentMedia')}>
-        <ExternalLinkEmbed
-          link={link}
-          onOpen={onOpen}
-          style={[a.mt_sm, style]}
-        />
-      </ContentHider>
-    )
-  }
-
-  // video embed
-  // =
-  if (AppBskyEmbedVideo.isView(embed)) {
-    return (
-      <ContentHider modui={moderation?.ui('contentMedia')}>
-        <VideoEmbed
-          embed={embed}
-          crop={
-            viewContext === PostEmbedViewContext.ThreadHighlighted
-              ? 'none'
-              : viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia
-              ? 'square'
-              : 'constrained'
-          }
-        />
-      </ContentHider>
-    )
-  }
-
-  return <View />
-}
-
-export function MaybeFeedCard({view}: {view: AppBskyFeedDefs.GeneratorView}) {
-  const pal = usePalette('default')
-  const moderationOpts = useModerationOpts()
-  const moderation = React.useMemo(() => {
-    return moderationOpts
-      ? moderateFeedGenerator(view, moderationOpts)
-      : undefined
-  }, [view, moderationOpts])
-
-  return (
-    <ContentHider modui={moderation?.ui('contentList')}>
-      <FeedSourceCard
-        feedUri={view.uri}
-        style={[pal.view, pal.border, styles.customFeedOuter]}
-        showLikes
-      />
-    </ContentHider>
-  )
-}
-
-export function MaybeListCard({view}: {view: AppBskyGraphDefs.ListView}) {
-  const moderationOpts = useModerationOpts()
-  const moderation = React.useMemo(() => {
-    return moderationOpts ? moderateUserList(view, moderationOpts) : undefined
-  }, [view, moderationOpts])
-  const t = useTheme()
-
-  return (
-    <ContentHider modui={moderation?.ui('contentList')}>
-      <View
-        style={[
-          a.border,
-          t.atoms.border_contrast_medium,
-          a.p_md,
-          a.rounded_sm,
-        ]}>
-        <ListCard.Default view={view} />
-      </View>
-    </ContentHider>
-  )
-}
-
-const styles = StyleSheet.create({
-  altContainer: {
-    backgroundColor: 'rgba(0, 0, 0, 0.75)',
-    borderRadius: 6,
-    paddingHorizontal: 6,
-    paddingVertical: 3,
-    position: 'absolute',
-    right: 6,
-    bottom: 6,
-  },
-  alt: {
-    color: 'white',
-    fontSize: 7,
-    fontWeight: '600',
-  },
-  customFeedOuter: {
-    borderWidth: StyleSheet.hairlineWidth,
-    borderRadius: 8,
-    paddingHorizontal: 12,
-    paddingVertical: 12,
-  },
-})
diff --git a/src/view/com/util/post-embeds/types.ts b/src/view/com/util/post-embeds/types.ts
deleted file mode 100644
index 08e903276..000000000
--- a/src/view/com/util/post-embeds/types.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-export enum PostEmbedViewContext {
-  ThreadHighlighted = 'ThreadHighlighted',
-  Feed = 'Feed',
-  FeedEmbedRecordWithMedia = 'FeedEmbedRecordWithMedia',
-}
-
-export enum QuoteEmbedViewContext {
-  FeedEmbedRecordWithMedia = PostEmbedViewContext.FeedEmbedRecordWithMedia,
-}