Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

[Video] use dynamic import for hls.js (#5429)

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>

authored by

Hailey
Dan Abramov
and committed by
GitHub
47301661 60b74435

+50 -10
+45 -8
src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb.tsx
··· 1 1 import React, {useEffect, useId, useRef, useState} from 'react' 2 2 import {View} from 'react-native' 3 3 import {AppBskyEmbedVideo} from '@atproto/api' 4 - import Hls, {Events, FragChangedData, Fragment} from 'hls.js' 4 + import type * as HlsTypes from 'hls.js' 5 5 6 6 import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' 7 7 import {atoms as a} from '#/alf' ··· 23 23 const videoRef = useRef<HTMLVideoElement>(null) 24 24 const [focused, setFocused] = useState(false) 25 25 const [hasSubtitleTrack, setHasSubtitleTrack] = useState(false) 26 + const [hlsLoading, setHlsLoading] = React.useState(false) 26 27 const figId = useId() 27 28 28 29 // send error up to error boundary ··· 37 38 setHasSubtitleTrack, 38 39 setError, 39 40 videoRef, 41 + setHlsLoading, 40 42 }) 41 43 42 44 return ( ··· 77 79 setActive={setActive} 78 80 focused={focused} 79 81 setFocused={setFocused} 82 + hlsLoading={hlsLoading} 80 83 onScreen={onScreen} 81 84 fullscreenRef={containerRef} 82 85 hasSubtitleTrack={hasSubtitleTrack} ··· 99 102 } 100 103 } 101 104 105 + type CachedPromise<T> = Promise<T> & {value: undefined | T} 106 + const promiseForHls = import( 107 + // @ts-ignore 108 + 'hls.js/dist/hls.min' 109 + ).then(mod => mod.default) as CachedPromise<typeof HlsTypes.default> 110 + promiseForHls.value = undefined 111 + promiseForHls.then(Hls => { 112 + promiseForHls.value = Hls 113 + }) 114 + 102 115 function useHLS({ 103 116 focused, 104 117 playlist, 105 118 setHasSubtitleTrack, 106 119 setError, 107 120 videoRef, 121 + setHlsLoading, 108 122 }: { 109 123 focused: boolean 110 124 playlist: string 111 125 setHasSubtitleTrack: (v: boolean) => void 112 126 setError: (v: Error | null) => void 113 127 videoRef: React.RefObject<HTMLVideoElement> 128 + setHlsLoading: (v: boolean) => void 114 129 }) { 115 - const hlsRef = useRef<Hls | undefined>(undefined) 116 - const [lowQualityFragments, setLowQualityFragments] = useState<Fragment[]>([]) 130 + const [Hls, setHls] = useState<typeof HlsTypes.default | undefined>( 131 + () => promiseForHls.value, 132 + ) 133 + useEffect(() => { 134 + if (!Hls) { 135 + setHlsLoading(true) 136 + promiseForHls.then(loadedHls => { 137 + setHls(() => loadedHls) 138 + setHlsLoading(false) 139 + }) 140 + } 141 + }, [Hls, setHlsLoading]) 142 + 143 + const hlsRef = useRef<HlsTypes.default | undefined>(undefined) 144 + const [lowQualityFragments, setLowQualityFragments] = useState< 145 + HlsTypes.Fragment[] 146 + >([]) 117 147 118 148 // purge low quality segments from buffer on next frag change 119 149 const handleFragChange = useNonReactiveCallback( 120 - (_event: Events.FRAG_CHANGED, {frag}: FragChangedData) => { 150 + ( 151 + _event: HlsTypes.Events.FRAG_CHANGED, 152 + {frag}: HlsTypes.FragChangedData, 153 + ) => { 154 + if (!Hls) return 121 155 if (!hlsRef.current) return 122 156 const hls = hlsRef.current 123 157 124 158 if (focused && hls.nextAutoLevel > 0) { 125 159 // if the current quality level goes above 0, flush the low quality segments 126 - const flushed: Fragment[] = [] 160 + const flushed: HlsTypes.Fragment[] = [] 127 161 128 162 for (const lowQualFrag of lowQualityFragments) { 129 163 // avoid if close to the current fragment ··· 147 181 148 182 useEffect(() => { 149 183 if (!videoRef.current) return 150 - if (!Hls.isSupported()) throw new HLSUnsupportedError() 184 + if (!Hls) return 185 + if (!Hls.isSupported()) { 186 + throw new HLSUnsupportedError() 187 + } 151 188 152 189 const hls = new Hls({ 153 190 maxMaxBufferLength: 10, // only load 10s ahead 154 191 // note: the amount buffered is affected by both maxBufferLength and maxBufferSize 155 - // it will buffer until it it's greater than *both* of those values 192 + // it will buffer until it is greater than *both* of those values 156 193 // so we use maxMaxBufferLength to set the actual maximum amount of buffering instead 157 194 }) 158 195 hlsRef.current = hls ··· 211 248 hls.destroy() 212 249 abortController.abort() 213 250 } 214 - }, [playlist, setError, setHasSubtitleTrack, videoRef, handleFragChange]) 251 + }, [playlist, setError, setHasSubtitleTrack, videoRef, handleFragChange, Hls]) 215 252 216 253 return hlsRef 217 254 }
+5 -2
src/view/com/util/post-embeds/VideoEmbedInner/web-controls/VideoControls.tsx
··· 43 43 setFocused, 44 44 onScreen, 45 45 fullscreenRef, 46 + hlsLoading, 46 47 hasSubtitleTrack, 47 48 }: { 48 49 videoRef: React.RefObject<HTMLVideoElement> ··· 53 54 setFocused: (focused: boolean) => void 54 55 onScreen: boolean 55 56 fullscreenRef: React.RefObject<HTMLDivElement> 57 + hlsLoading: boolean 56 58 hasSubtitleTrack: boolean 57 59 }) { 58 60 const { ··· 80 82 const [isFullscreen, toggleFullscreen] = useFullscreen(fullscreenRef) 81 83 const {state: hasFocus, onIn: onFocus, onOut: onBlur} = useInteractionState() 82 84 const [interactingViaKeypress, setInteractingViaKeypress] = useState(false) 85 + const showSpinner = hlsLoading || buffering 83 86 const { 84 87 state: volumeHovered, 85 88 onIn: onVolumeHover, ··· 409 412 )} 410 413 </View> 411 414 </View> 412 - {(buffering || error) && ( 415 + {(showSpinner || error) && ( 413 416 <View 414 417 pointerEvents="none" 415 418 style={[a.absolute, a.inset_0, a.justify_center, a.align_center]}> 416 - {buffering && <Loader fill={t.palette.white} size="lg" />} 419 + {showSpinner && <Loader fill={t.palette.white} size="lg" />} 417 420 {error && ( 418 421 <Text style={{color: t.palette.white}}> 419 422 <Trans>An error occurred</Trans>