diff options
author | Hailey <me@haileyok.com> | 2024-09-25 09:51:51 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-09-25 09:51:51 -0700 |
commit | 47301661f786f032c5b2f20773a5ee9041fed64e (patch) | |
tree | a2fa925f9f6363cdfa4e55acfbb96c60a6ef0d7b /src | |
parent | 60b74435358d19322e5e4d08c45e48f58cd1efb1 (diff) | |
download | voidsky-47301661f786f032c5b2f20773a5ee9041fed64e.tar.zst |
[Video] use dynamic import for hls.js (#5429)
Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb.tsx | 53 | ||||
-rw-r--r-- | src/view/com/util/post-embeds/VideoEmbedInner/web-controls/VideoControls.tsx | 7 |
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> |