fix video
This commit is contained in:
@@ -10,7 +10,6 @@ import PlaybackItem from "./components/PlaybackItem.web";
|
||||
const Playbacks = () => {
|
||||
const { height: windowHeight } = useWindowDimensions();
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
const carouselRef = useRef(null);
|
||||
const route = useRoute();
|
||||
const focusProjectId =
|
||||
route?.params?.projectId || route?.params?.focusId || null;
|
||||
@@ -25,15 +24,132 @@ const Playbacks = () => {
|
||||
batchSize: 6,
|
||||
});
|
||||
|
||||
const activePlayback = playbacks?.[activeIndex] || null;
|
||||
const activeVideoUrl = activePlayback?.playbackUrl || null;
|
||||
const backgroundVideoRef = useRef(null);
|
||||
const lastActiveIndexRef = useRef(0);
|
||||
const backgroundSeekPendingRef = useRef(false);
|
||||
const listRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const total = playbacks?.length || 0;
|
||||
if (total <= 0) {
|
||||
if (activeIndex !== 0) {
|
||||
lastActiveIndexRef.current = 0;
|
||||
setActiveIndex(0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const maxIndex = total - 1;
|
||||
if (activeIndex > maxIndex) {
|
||||
lastActiveIndexRef.current = maxIndex;
|
||||
setActiveIndex(maxIndex);
|
||||
}
|
||||
}, [playbacks?.length, activeIndex]);
|
||||
|
||||
const handleActiveIndexChange = useCallback(
|
||||
(rawIndex) => {
|
||||
if (Number.isNaN(rawIndex)) return;
|
||||
const total = playbacks?.length || 0;
|
||||
const maxIndex = Math.max(0, total - 1);
|
||||
const clamped = Math.max(0, Math.min(rawIndex, maxIndex));
|
||||
if (clamped === lastActiveIndexRef.current) return;
|
||||
lastActiveIndexRef.current = clamped;
|
||||
setActiveIndex((prev) => (prev === clamped ? prev : clamped));
|
||||
if (total > 0 && clamped >= Math.max(0, total - 6)) {
|
||||
loadMore?.();
|
||||
}
|
||||
},
|
||||
[playbacks?.length, loadMore]
|
||||
);
|
||||
|
||||
const syncBackgroundVideo = useCallback(({ currentTime, isPlaying }) => {
|
||||
const bg = backgroundVideoRef.current;
|
||||
if (!bg) return;
|
||||
|
||||
if (typeof currentTime === "number" && Number.isFinite(currentTime)) {
|
||||
const applyTime = () => {
|
||||
backgroundSeekPendingRef.current = false;
|
||||
const diff = Math.abs((bg.currentTime || 0) - currentTime);
|
||||
if (diff > 0.25) {
|
||||
try {
|
||||
bg.currentTime = currentTime;
|
||||
} catch (_e) {}
|
||||
}
|
||||
};
|
||||
|
||||
if (bg.readyState >= 1) applyTime();
|
||||
else if (!backgroundSeekPendingRef.current) {
|
||||
backgroundSeekPendingRef.current = true;
|
||||
const handler = () => applyTime();
|
||||
bg.addEventListener("loadeddata", handler, { once: true });
|
||||
}
|
||||
}
|
||||
|
||||
if (isPlaying === true) {
|
||||
if (bg.paused) {
|
||||
bg.play().catch(() => {});
|
||||
}
|
||||
} else if (isPlaying === false) {
|
||||
try {
|
||||
if (!bg.paused) bg.pause();
|
||||
} catch (_e) {}
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const bg = backgroundVideoRef.current;
|
||||
if (!bg) return;
|
||||
if (!activeVideoUrl) {
|
||||
backgroundSeekPendingRef.current = false;
|
||||
try {
|
||||
bg.pause();
|
||||
} catch (_e) {}
|
||||
bg.removeAttribute?.("src");
|
||||
bg.load?.();
|
||||
return;
|
||||
}
|
||||
backgroundSeekPendingRef.current = false;
|
||||
const setSrcIfNeeded = () => {
|
||||
const attrSrc = bg.getAttribute("src");
|
||||
if (attrSrc !== activeVideoUrl) {
|
||||
bg.setAttribute("src", activeVideoUrl);
|
||||
try {
|
||||
bg.load();
|
||||
} catch (_e) {}
|
||||
}
|
||||
};
|
||||
|
||||
const startPlayback = () => {
|
||||
bg.play().catch(() => {});
|
||||
};
|
||||
|
||||
setSrcIfNeeded();
|
||||
|
||||
if (bg.readyState >= 2) {
|
||||
startPlayback();
|
||||
return;
|
||||
}
|
||||
|
||||
const handleLoaded = () => {
|
||||
startPlayback();
|
||||
};
|
||||
bg.addEventListener("loadeddata", handleLoaded, { once: true });
|
||||
return () => {
|
||||
bg.removeEventListener("loadeddata", handleLoaded);
|
||||
};
|
||||
}, [activeVideoUrl]);
|
||||
|
||||
const triedLoadMoreRef = useRef(0);
|
||||
useEffect(() => {
|
||||
if (!focusProjectId) return;
|
||||
const idx = playbacks.findIndex((p) => p?.id === focusProjectId);
|
||||
if (idx >= 0) {
|
||||
lastActiveIndexRef.current = idx;
|
||||
setActiveIndex(idx);
|
||||
setTimeout(() => {
|
||||
try {
|
||||
carouselRef.current?.scrollTo?.({ index: idx, animated: false });
|
||||
listRef.current?.scrollToIndex?.({ index: idx, animated: false });
|
||||
} catch (_e) {}
|
||||
}, 50);
|
||||
} else if (loadMore && triedLoadMoreRef.current < 6) {
|
||||
@@ -43,22 +159,27 @@ const Playbacks = () => {
|
||||
}, [focusProjectId, playbacks, loadMore]);
|
||||
|
||||
// === FlatList (one real page per item) ===
|
||||
const listRef = useRef(null);
|
||||
|
||||
// keep active index in sync with scroll
|
||||
const onMomentumScrollEnd = useCallback(
|
||||
(e) => {
|
||||
try {
|
||||
const y = e?.nativeEvent?.contentOffset?.y || 0;
|
||||
const idx = Math.round(y / windowHeight);
|
||||
if (!Number.isNaN(idx))
|
||||
setActiveIndex(
|
||||
Math.max(0, Math.min(idx, (playbacks?.length || 1) - 1))
|
||||
);
|
||||
if (idx >= (playbacks?.length || 0) - 6) loadMore?.();
|
||||
handleActiveIndexChange(idx);
|
||||
} catch (_e) {}
|
||||
},
|
||||
[windowHeight, playbacks?.length, loadMore]
|
||||
[windowHeight, handleActiveIndexChange]
|
||||
);
|
||||
|
||||
const onScroll = useCallback(
|
||||
(e) => {
|
||||
try {
|
||||
const y = e?.nativeEvent?.contentOffset?.y || 0;
|
||||
const idx = Math.round(y / windowHeight);
|
||||
handleActiveIndexChange(idx);
|
||||
} catch (_e) {}
|
||||
},
|
||||
[windowHeight, handleActiveIndexChange]
|
||||
);
|
||||
|
||||
// programmatic jump to focus item
|
||||
@@ -66,6 +187,7 @@ const Playbacks = () => {
|
||||
if (!focusProjectId) return;
|
||||
const idx = playbacks.findIndex((p) => p?.id === focusProjectId);
|
||||
if (idx >= 0) {
|
||||
lastActiveIndexRef.current = idx;
|
||||
setActiveIndex(idx);
|
||||
setTimeout(() => {
|
||||
try {
|
||||
@@ -85,8 +207,36 @@ const Playbacks = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<Page headerType="NONE" width="100%" containerStyle={{ padding: 0 }}>
|
||||
<Page
|
||||
headerType="NONE"
|
||||
width="100%"
|
||||
containerStyle={{ padding: 0 }}
|
||||
>
|
||||
<View style={styles.screen}>
|
||||
{activeVideoUrl ? (
|
||||
<View pointerEvents="none" style={styles.backgroundVideoWrapper}>
|
||||
<video
|
||||
ref={backgroundVideoRef}
|
||||
key={activeVideoUrl}
|
||||
src={activeVideoUrl}
|
||||
autoPlay
|
||||
muted
|
||||
loop
|
||||
playsInline
|
||||
preload="auto"
|
||||
style={{
|
||||
position: "absolute",
|
||||
inset: 0,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
objectFit: "cover",
|
||||
filter: "blur(28px) saturate(120%) brightness(55%)",
|
||||
transform: "scale(1.1)",
|
||||
}}
|
||||
/>
|
||||
<View style={styles.backgroundOverlay} />
|
||||
</View>
|
||||
) : null}
|
||||
<FlatList
|
||||
ref={listRef}
|
||||
data={playbacks}
|
||||
@@ -98,6 +248,7 @@ const Playbacks = () => {
|
||||
userCache={userCache}
|
||||
getUserByUid={getUserByUid}
|
||||
isActive={isFocused && index === activeIndex}
|
||||
onBackgroundSync={syncBackgroundVideo}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
@@ -108,7 +259,8 @@ const Playbacks = () => {
|
||||
initialNumToRender={3}
|
||||
windowSize={5}
|
||||
removeClippedSubviews
|
||||
scrollEventThrottle={32}
|
||||
onScroll={onScroll}
|
||||
scrollEventThrottle={16}
|
||||
/>
|
||||
</View>
|
||||
</Page>
|
||||
@@ -121,5 +273,16 @@ const styles = StyleSheet.create({
|
||||
screen: {
|
||||
flex: 1,
|
||||
overscrollBehavior: "none",
|
||||
position: "relative",
|
||||
},
|
||||
backgroundVideoWrapper: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
overflow: "hidden",
|
||||
zIndex: -1,
|
||||
backgroundColor: "#060606",
|
||||
},
|
||||
backgroundOverlay: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
backgroundColor: "rgba(2, 2, 2, 0.35)",
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user