Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

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

[Video] Flush low quality segments once focused (#5430)

* Update VideoEmbedInnerWeb.tsx

* keep proper track and flush properly

* consistent current

* use current in listener

* manually loop

authored by

Samuel Newman and committed by
GitHub
91853ed5 5eb29448

+69 -8
+69 -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 from 'hls.js' 4 + import Hls, {Events, FragChangedData, Fragment} from 'hls.js' 5 5 6 6 import {atoms as a} from '#/alf' 7 7 import {MediaInsetBorder} from '#/components/MediaInsetBorder' ··· 19 19 onScreen: boolean 20 20 }) { 21 21 const containerRef = useRef<HTMLDivElement>(null) 22 - const ref = useRef<HTMLVideoElement>(null) 22 + const videoRef = useRef<HTMLVideoElement>(null) 23 23 const [focused, setFocused] = useState(false) 24 24 const [hasSubtitleTrack, setHasSubtitleTrack] = useState(false) 25 25 const figId = useId() ··· 31 31 } 32 32 33 33 const hlsRef = useRef<Hls | undefined>(undefined) 34 + const [lowQualityFragments, setLowQualityFragments] = useState<Fragment[]>([]) 34 35 35 36 useEffect(() => { 36 - if (!ref.current) return 37 + if (!videoRef.current) return 37 38 if (!Hls.isSupported()) throw new HLSUnsupportedError() 38 39 39 40 const hls = new Hls({ 40 - capLevelToPlayerSize: true, 41 41 maxMaxBufferLength: 10, // only load 10s ahead 42 42 // note: the amount buffered is affected by both maxBufferLength and maxBufferSize 43 43 // it will buffer until it it's greater than *both* of those values ··· 45 45 }) 46 46 hlsRef.current = hls 47 47 48 - hls.attachMedia(ref.current) 48 + hls.attachMedia(videoRef.current) 49 49 hls.loadSource(embed.playlist) 50 50 51 51 // initial value, later on it's managed by Controls 52 52 hls.autoLevelCapping = 0 53 53 54 + // manually loop, so if we've flushed the first buffer it doesn't get confused 55 + const abortController = new AbortController() 56 + const {signal} = abortController 57 + videoRef.current.addEventListener( 58 + 'ended', 59 + function () { 60 + this.currentTime = 0 61 + this.play() 62 + }, 63 + {signal}, 64 + ) 65 + 54 66 hls.on(Hls.Events.SUBTITLE_TRACKS_UPDATED, (_event, data) => { 55 67 if (data.subtitleTracks.length > 0) { 56 68 setHasSubtitleTrack(true) 69 + } 70 + }) 71 + 72 + hls.on(Hls.Events.FRAG_BUFFERED, (_event, {frag}) => { 73 + if (frag.level === 0) { 74 + setLowQualityFragments(prev => [...prev, frag]) 57 75 } 58 76 }) 59 77 ··· 67 85 } else { 68 86 setError(data.error) 69 87 } 88 + } else { 89 + console.error(data.error) 70 90 } 71 91 }) 72 92 ··· 74 94 hlsRef.current = undefined 75 95 hls.detachMedia() 76 96 hls.destroy() 97 + abortController.abort() 77 98 } 78 99 }, [embed.playlist]) 79 100 101 + // purge low quality segments from buffer on next frag change 102 + useEffect(() => { 103 + if (!hlsRef.current) return 104 + 105 + const current = hlsRef.current 106 + 107 + if (focused) { 108 + function fragChanged( 109 + _event: Events.FRAG_CHANGED, 110 + {frag}: FragChangedData, 111 + ) { 112 + // if the current quality level goes above 0, flush the low quality segments 113 + if (current.nextAutoLevel > 0) { 114 + const flushed: Fragment[] = [] 115 + 116 + for (const lowQualFrag of lowQualityFragments) { 117 + // avoid if close to the current fragment 118 + if (Math.abs(frag.start - lowQualFrag.start) < 0.1) { 119 + return 120 + } 121 + 122 + current.trigger(Hls.Events.BUFFER_FLUSHING, { 123 + startOffset: lowQualFrag.start, 124 + endOffset: lowQualFrag.end, 125 + type: 'video', 126 + }) 127 + 128 + flushed.push(lowQualFrag) 129 + } 130 + 131 + setLowQualityFragments(prev => prev.filter(f => !flushed.includes(f))) 132 + } 133 + } 134 + current.on(Hls.Events.FRAG_CHANGED, fragChanged) 135 + 136 + return () => { 137 + current.off(Hls.Events.FRAG_CHANGED, fragChanged) 138 + } 139 + } 140 + }, [focused, lowQualityFragments]) 141 + 80 142 return ( 81 143 <View style={[a.flex_1, a.rounded_md, a.overflow_hidden]}> 82 144 <div ref={containerRef} style={{height: '100%', width: '100%'}}> 83 145 <figure style={{margin: 0, position: 'absolute', inset: 0}}> 84 146 <video 85 - ref={ref} 147 + ref={videoRef} 86 148 poster={embed.thumbnail} 87 149 style={{width: '100%', height: '100%', objectFit: 'contain'}} 88 150 playsInline 89 151 preload="none" 90 - loop 91 152 muted={!focused} 92 153 aria-labelledby={embed.alt ? figId : undefined} 93 154 /> ··· 110 171 )} 111 172 </figure> 112 173 <Controls 113 - videoRef={ref} 174 + videoRef={videoRef} 114 175 hlsRef={hlsRef} 115 176 active={active} 116 177 setActive={setActive}