about summary refs log tree commit diff
diff options
context:
space:
mode:
authorHailey <me@haileyok.com>2024-09-25 09:51:51 -0700
committerGitHub <noreply@github.com>2024-09-25 09:51:51 -0700
commit47301661f786f032c5b2f20773a5ee9041fed64e (patch)
treea2fa925f9f6363cdfa4e55acfbb96c60a6ef0d7b
parent60b74435358d19322e5e4d08c45e48f58cd1efb1 (diff)
downloadvoidsky-47301661f786f032c5b2f20773a5ee9041fed64e.tar.zst
[Video] use dynamic import for hls.js (#5429)
Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
-rw-r--r--src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb.tsx53
-rw-r--r--src/view/com/util/post-embeds/VideoEmbedInner/web-controls/VideoControls.tsx7
2 files changed, 50 insertions, 10 deletions
diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb.tsx
index b49c49e4a..fa2b7e3d3 100644
--- a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb.tsx
+++ b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb.tsx
@@ -1,7 +1,7 @@
 import React, {useEffect, useId, useRef, useState} from 'react'
 import {View} from 'react-native'
 import {AppBskyEmbedVideo} from '@atproto/api'
-import Hls, {Events, FragChangedData, Fragment} from 'hls.js'
+import type * as HlsTypes from 'hls.js'
 
 import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
 import {atoms as a} from '#/alf'
@@ -23,6 +23,7 @@ export function VideoEmbedInnerWeb({
   const videoRef = useRef<HTMLVideoElement>(null)
   const [focused, setFocused] = useState(false)
   const [hasSubtitleTrack, setHasSubtitleTrack] = useState(false)
+  const [hlsLoading, setHlsLoading] = React.useState(false)
   const figId = useId()
 
   // send error up to error boundary
@@ -37,6 +38,7 @@ export function VideoEmbedInnerWeb({
     setHasSubtitleTrack,
     setError,
     videoRef,
+    setHlsLoading,
   })
 
   return (
@@ -77,6 +79,7 @@ export function VideoEmbedInnerWeb({
           setActive={setActive}
           focused={focused}
           setFocused={setFocused}
+          hlsLoading={hlsLoading}
           onScreen={onScreen}
           fullscreenRef={containerRef}
           hasSubtitleTrack={hasSubtitleTrack}
@@ -99,31 +102,62 @@ export class VideoNotFoundError extends Error {
   }
 }
 
+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({
   focused,
   playlist,
   setHasSubtitleTrack,
   setError,
   videoRef,
+  setHlsLoading,
 }: {
   focused: boolean
   playlist: string
   setHasSubtitleTrack: (v: boolean) => void
   setError: (v: Error | null) => void
   videoRef: React.RefObject<HTMLVideoElement>
+  setHlsLoading: (v: boolean) => void
 }) {
-  const hlsRef = useRef<Hls | undefined>(undefined)
-  const [lowQualityFragments, setLowQualityFragments] = useState<Fragment[]>([])
+  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: Events.FRAG_CHANGED, {frag}: FragChangedData) => {
+    (
+      _event: HlsTypes.Events.FRAG_CHANGED,
+      {frag}: HlsTypes.FragChangedData,
+    ) => {
+      if (!Hls) return
       if (!hlsRef.current) return
       const hls = hlsRef.current
 
       if (focused && hls.nextAutoLevel > 0) {
         // if the current quality level goes above 0, flush the low quality segments
-        const flushed: Fragment[] = []
+        const flushed: HlsTypes.Fragment[] = []
 
         for (const lowQualFrag of lowQualityFragments) {
           // avoid if close to the current fragment
@@ -147,12 +181,15 @@ function useHLS({
 
   useEffect(() => {
     if (!videoRef.current) return
-    if (!Hls.isSupported()) throw new HLSUnsupportedError()
+    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 it's greater than *both* of those values
+      // 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
@@ -211,7 +248,7 @@ function useHLS({
       hls.destroy()
       abortController.abort()
     }
-  }, [playlist, setError, setHasSubtitleTrack, videoRef, handleFragChange])
+  }, [playlist, setError, setHasSubtitleTrack, videoRef, handleFragChange, Hls])
 
   return hlsRef
 }
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
index 2d1427347..dd0dafc33 100644
--- a/src/view/com/util/post-embeds/VideoEmbedInner/web-controls/VideoControls.tsx
+++ b/src/view/com/util/post-embeds/VideoEmbedInner/web-controls/VideoControls.tsx
@@ -43,6 +43,7 @@ export function Controls({
   setFocused,
   onScreen,
   fullscreenRef,
+  hlsLoading,
   hasSubtitleTrack,
 }: {
   videoRef: React.RefObject<HTMLVideoElement>
@@ -53,6 +54,7 @@ export function Controls({
   setFocused: (focused: boolean) => void
   onScreen: boolean
   fullscreenRef: React.RefObject<HTMLDivElement>
+  hlsLoading: boolean
   hasSubtitleTrack: boolean
 }) {
   const {
@@ -80,6 +82,7 @@ export function Controls({
   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,
@@ -409,11 +412,11 @@ export function Controls({
           )}
         </View>
       </View>
-      {(buffering || error) && (
+      {(showSpinner || error) && (
         <View
           pointerEvents="none"
           style={[a.absolute, a.inset_0, a.justify_center, a.align_center]}>
-          {buffering && <Loader fill={t.palette.white} size="lg" />}
+          {showSpinner && <Loader fill={t.palette.white} size="lg" />}
           {error && (
             <Text style={{color: t.palette.white}}>
               <Trans>An error occurred</Trans>