Live video on the AT Protocol

Merge pull request #944 from streamplace/eli/android-no-play

fix mobile playback issues

authored by

Eli Mallon and committed by
GitHub
cf20c90a 28ca16a1

+93 -95
+17 -1
js/app/components/mobile/ui.tsx
··· 1 1 import { useNavigation } from "@react-navigation/native"; 2 2 import { 3 3 ContentWarningBadge, 4 + PlayerStatus, 4 5 PlayerUI, 5 6 Slider, 6 7 Text, ··· 80 81 81 82 const muteWasForced = usePlayerStore((state) => state.muteWasForced); 82 83 const setMuteWasForced = usePlayerStore((state) => state.setMuteWasForced); 84 + const [playerIsReady, setPlayerIsReady] = useState(false); 85 + const playerStatusReady = usePlayerStore( 86 + (state) => state.status === PlayerStatus.PLAYING, 87 + ); 88 + useEffect(() => { 89 + if (playerIsReady) return; 90 + if (playerStatusReady) { 91 + setPlayerIsReady(true); 92 + } else { 93 + const handle = setTimeout(() => { 94 + setPlayerIsReady(true); 95 + }, 5000); 96 + return () => clearTimeout(handle); 97 + } 98 + }, [playerStatusReady]); 83 99 const muted = useMuted(); 84 100 const setMuted = useSetMuted(); 85 101 const ls = useLivestream(); ··· 268 284 <PlayerUI.AutoplayButton /> 269 285 </View> 270 286 </GestureDetector> 271 - {showChat === undefined && !isSelfAndNotLive && ( 287 + {showChat === undefined && !isSelfAndNotLive && playerIsReady && ( 272 288 <MobileChatPanel isPlayerRatioGreater={isPlayerRatioGreater} /> 273 289 )} 274 290 </>
+70 -84
js/components/src/components/chat/chat.tsx
··· 142 142 }, 143 143 ); 144 144 145 - const ChatLine = memo( 146 - ({ 147 - item, 148 - isHovered, 149 - onHoverIn, 150 - onHoverOut, 151 - hoverTimeoutRef, 152 - }: { 153 - item: ChatMessageViewHydrated; 154 - isHovered?: boolean; 155 - onHoverIn?: () => void; 156 - onHoverOut?: () => void; 157 - hoverTimeoutRef?: React.MutableRefObject<NodeJS.Timeout | null>; 158 - }) => { 159 - const setReply = useSetReplyToMessage(); 160 - const setModMsg = usePlayerStore((state) => state.setModMessage); 161 - const swipeableRef = useRef<SwipeableMethods | null>(null); 145 + const ChatLine = memo(({ item }: { item: ChatMessageViewHydrated }) => { 146 + const setReply = useSetReplyToMessage(); 147 + const setModMsg = usePlayerStore((state) => state.setModMessage); 148 + const swipeableRef = useRef<SwipeableMethods | null>(null); 149 + const [isHovered, setIsHovered] = useState(false); 150 + const hoverTimeoutRef = useRef<NodeJS.Timeout | null>(null); 162 151 163 - if (item.author.did === "did:sys:system") { 164 - return ( 165 - <SystemMessage 166 - variant={getSystemMessageType(item) || SystemMessageType.notification} 167 - timestamp={new Date(item.record.createdAt)} 168 - title={item.record.text} 169 - facets={item.record.facets} 170 - /> 171 - ); 152 + const handleHoverIn = () => { 153 + if (hoverTimeoutRef.current) { 154 + clearTimeout(hoverTimeoutRef.current); 155 + hoverTimeoutRef.current = null; 172 156 } 157 + setIsHovered(true); 158 + }; 173 159 174 - if (Platform.OS === "web") { 175 - return ( 176 - <View 177 - style={[ 178 - py[1], 179 - px[2], 180 - { 181 - position: "relative", 182 - borderRadius: 8, 183 - minWidth: 0, 184 - maxWidth: "100%", 185 - }, 186 - isHovered && bg.gray[950], 187 - ]} 188 - onPointerEnter={onHoverIn} 189 - onPointerLeave={onHoverOut} 190 - > 191 - <Pressable style={[{ minWidth: 0, maxWidth: "100%" }]}> 192 - <RenderChatMessage item={item} /> 193 - </Pressable> 194 - <ActionsBar 195 - item={item} 196 - visible={!!isHovered} 197 - hoverTimeoutRef={hoverTimeoutRef!} 198 - /> 199 - </View> 200 - ); 201 - } 160 + const handleHoverOut = () => { 161 + hoverTimeoutRef.current = setTimeout(() => { 162 + setIsHovered(false); 163 + }, 50); 164 + }; 202 165 166 + useEffect(() => { 167 + return () => { 168 + if (hoverTimeoutRef.current) { 169 + clearTimeout(hoverTimeoutRef.current); 170 + } 171 + }; 172 + }, []); 173 + 174 + if (item.author.did === "did:sys:system") { 203 175 return ( 176 + <SystemMessage 177 + variant={getSystemMessageType(item) || SystemMessageType.notification} 178 + timestamp={new Date(item.record.createdAt)} 179 + title={item.record.text} 180 + facets={item.record.facets} 181 + /> 182 + ); 183 + } 184 + 185 + if (Platform.OS === "web") { 186 + return ( 187 + <View 188 + style={[ 189 + py[1], 190 + px[2], 191 + { 192 + position: "relative", 193 + borderRadius: 8, 194 + minWidth: 0, 195 + maxWidth: "100%", 196 + }, 197 + isHovered && bg.gray[950], 198 + ]} 199 + onPointerEnter={handleHoverIn} 200 + onPointerLeave={handleHoverOut} 201 + > 202 + <Pressable style={[{ minWidth: 0, maxWidth: "100%" }]}> 203 + <RenderChatMessage item={item} /> 204 + </Pressable> 205 + <ActionsBar 206 + item={item} 207 + visible={isHovered} 208 + hoverTimeoutRef={hoverTimeoutRef} 209 + /> 210 + </View> 211 + ); 212 + } 213 + 214 + return ( 215 + <> 204 216 <Swipeable 205 217 containerStyle={[py[1]]} 206 218 friction={2} ··· 227 239 > 228 240 <RenderChatMessage item={item} /> 229 241 </Swipeable> 230 - ); 231 - }, 232 - ); 242 + </> 243 + ); 244 + }); 233 245 234 246 export function Chat({ 235 247 shownMessages = SHOWN_MSGS, ··· 243 255 const chat = useChat(); 244 256 const [isScrolledUp, setIsScrolledUp] = useState(false); 245 257 const flatListRef = useRef<FlatList>(null); 246 - const [hoveredMessageUri, setHoveredMessageUri] = useState<string | null>( 247 - null, 248 - ); 249 - const hoverTimeoutRef = useRef<NodeJS.Timeout | null>(null); 250 - 251 - const handleHoverIn = (uri: string) => { 252 - if (hoverTimeoutRef.current) { 253 - clearTimeout(hoverTimeoutRef.current); 254 - hoverTimeoutRef.current = null; 255 - } 256 - setHoveredMessageUri(uri); 257 - }; 258 - 259 - const handleHoverOut = () => { 260 - hoverTimeoutRef.current = setTimeout(() => { 261 - setHoveredMessageUri(null); 262 - }, 50); 263 - }; 264 258 265 259 // Animation for scroll-to-bottom button 266 260 const buttonOpacity = useSharedValue(0); ··· 327 321 data={chat.slice(0, shownMessages)} 328 322 inverted={true} 329 323 keyExtractor={keyExtractor} 330 - renderItem={({ item }) => ( 331 - <ChatLine 332 - item={item} 333 - isHovered={hoveredMessageUri === item.uri} 334 - onHoverIn={() => handleHoverIn(item.uri)} 335 - onHoverOut={handleHoverOut} 336 - hoverTimeoutRef={hoverTimeoutRef} 337 - /> 338 - )} 324 + renderItem={({ item, index }) => <ChatLine item={item} />} 339 325 removeClippedSubviews={true} 340 326 maxToRenderPerBatch={10} 341 327 initialNumToRender={10}
+1 -5
js/components/src/components/mobile-player/video-async.native.tsx
··· 186 186 }) { 187 187 const selectedRendition = usePlayerStore((x) => x.selectedRendition); 188 188 const src = usePlayerStore((x) => x.src); 189 - const { url } = srcToUrl( 190 - { src: src, selectedRendition }, 191 - PlayerProtocol.WEBRTC, 192 - ); 193 - const [stream, stuck] = useWebRTC(url); 189 + const [stream, stuck] = useWebRTC(src); 194 190 const status = usePlayerStore((x) => x.status); 195 191 196 192 const setPlayerWidth = usePlayerStore((x) => x.setPlayerWidth);
+1 -1
js/components/src/components/mobile-player/video-retry.tsx
··· 8 8 9 9 useEffect(() => { 10 10 if (!playing) { 11 - const jitter = 500 + Math.random() * 1500; 11 + const jitter = 2000 + Math.random() * 1500; 12 12 retryTimeoutRef.current = setTimeout(() => { 13 13 console.log("Retrying video playback..."); 14 14 setRetries((prevRetries) => prevRetries + 1);
+4 -4
pkg/director/stream_session.go
··· 473 473 func (ss *StreamSession) UpdateLivestream(ctx context.Context, repoDID string) { 474 474 select { 475 475 case ss.livestreamUpdateChan <- struct{}{}: 476 - log.Warn(ctx, "livestream update signal sent") 476 + log.Debug(ctx, "livestream update signal sent") 477 477 default: 478 - log.Warn(ctx, "livestream update channel full, signal already pending") 478 + log.Debug(ctx, "livestream update channel full, signal already pending") 479 479 // Channel full, signal already pending 480 480 } 481 481 } ··· 489 489 return nil 490 490 case <-ss.livestreamUpdateChan: 491 491 if time.Since(ss.lastLivestreamTime) < livestreamUpdateInterval { 492 - log.Warn(ctx, "not updating livestream, last livestream was less than 30 seconds ago") 492 + log.Debug(ctx, "not updating livestream, last livestream was less than 30 seconds ago") 493 493 continue 494 494 } 495 495 if err := ss.doUpdateLivestream(ctx, repoDID); err != nil { ··· 547 547 return fmt.Errorf("could not update livestream record: %w", err) 548 548 } 549 549 550 - log.Warn(ctx, "updated livestream record", "uri", lastLivestream.URI) 550 + log.Debug(ctx, "updated livestream record", "uri", lastLivestream.URI) 551 551 ss.lastLivestreamTime = time.Now() 552 552 553 553 return nil