mirror of
https://gh-proxy.org/https://github.com/tiajinsha/JKVideo
synced 2026-07-08 07:28:37 +08:00
1
This commit is contained in:
@@ -37,6 +37,7 @@ export function BigVideoCard({ item, isVisible, onPress }: Props) {
|
|||||||
const [videoUrl, setVideoUrl] = useState<string | undefined>();
|
const [videoUrl, setVideoUrl] = useState<string | undefined>();
|
||||||
const [isDash, setIsDash] = useState(false);
|
const [isDash, setIsDash] = useState(false);
|
||||||
const [paused, setPaused] = useState(true);
|
const [paused, setPaused] = useState(true);
|
||||||
|
const [muted, setMuted] = useState(true);
|
||||||
const thumbOpacity = useRef(new Animated.Value(1)).current;
|
const thumbOpacity = useRef(new Animated.Value(1)).current;
|
||||||
|
|
||||||
// Reset video state when the item changes
|
// Reset video state when the item changes
|
||||||
@@ -44,6 +45,7 @@ export function BigVideoCard({ item, isVisible, onPress }: Props) {
|
|||||||
setVideoUrl(undefined);
|
setVideoUrl(undefined);
|
||||||
setIsDash(false);
|
setIsDash(false);
|
||||||
setPaused(true);
|
setPaused(true);
|
||||||
|
setMuted(true);
|
||||||
thumbOpacity.setValue(1);
|
thumbOpacity.setValue(1);
|
||||||
}, [item.bvid]);
|
}, [item.bvid]);
|
||||||
|
|
||||||
@@ -88,6 +90,7 @@ export function BigVideoCard({ item, isVisible, onPress }: Props) {
|
|||||||
if (!videoUrl) return;
|
if (!videoUrl) return;
|
||||||
setPaused(!isVisible);
|
setPaused(!isVisible);
|
||||||
if (!isVisible) {
|
if (!isVisible) {
|
||||||
|
setMuted(true);
|
||||||
// Restore thumbnail when leaving viewport
|
// Restore thumbnail when leaving viewport
|
||||||
Animated.timing(thumbOpacity, {
|
Animated.timing(thumbOpacity, {
|
||||||
toValue: 1,
|
toValue: 1,
|
||||||
@@ -111,9 +114,28 @@ export function BigVideoCard({ item, isVisible, onPress }: Props) {
|
|||||||
<TouchableOpacity style={styles.card} onPress={onPress} activeOpacity={0.9}>
|
<TouchableOpacity style={styles.card} onPress={onPress} activeOpacity={0.9}>
|
||||||
{/* Media area */}
|
{/* Media area */}
|
||||||
<View style={[mediaDimensions, { position: "relative" }]}>
|
<View style={[mediaDimensions, { position: "relative" }]}>
|
||||||
{/* Thumbnail */}
|
{/* Video player — rendered first so it sits behind the thumbnail */}
|
||||||
|
{videoUrl && (
|
||||||
|
<Video
|
||||||
|
source={
|
||||||
|
isDash
|
||||||
|
? { uri: videoUrl, type: "mpd", headers: HEADERS }
|
||||||
|
: { uri: videoUrl, headers: HEADERS }
|
||||||
|
}
|
||||||
|
style={StyleSheet.absoluteFill}
|
||||||
|
resizeMode="cover"
|
||||||
|
muted={muted}
|
||||||
|
paused={paused}
|
||||||
|
repeat
|
||||||
|
controls={false}
|
||||||
|
onReadyForDisplay={handleVideoReady}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Thumbnail — on top of Video, fades out once video is ready */}
|
||||||
<Animated.View
|
<Animated.View
|
||||||
style={[StyleSheet.absoluteFill, { opacity: thumbOpacity }]}
|
style={[StyleSheet.absoluteFill, { opacity: thumbOpacity }]}
|
||||||
|
pointerEvents="none"
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
source={{ uri: proxyImageUrl(item.pic) }}
|
source={{ uri: proxyImageUrl(item.pic) }}
|
||||||
@@ -136,22 +158,19 @@ export function BigVideoCard({ item, isVisible, onPress }: Props) {
|
|||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Video player — only mounted when URL is available */}
|
{/* Mute toggle — visible only when video is playing */}
|
||||||
{videoUrl && (
|
{videoUrl && !paused && (
|
||||||
<Video
|
<TouchableOpacity
|
||||||
source={
|
style={styles.muteBtn}
|
||||||
isDash
|
onPress={() => setMuted((m) => !m)}
|
||||||
? { uri: videoUrl, type: "mpd", headers: HEADERS }
|
activeOpacity={0.7}
|
||||||
: { uri: videoUrl, headers: HEADERS }
|
>
|
||||||
}
|
<Ionicons
|
||||||
style={StyleSheet.absoluteFill}
|
name={muted ? "volume-mute" : "volume-high"}
|
||||||
resizeMode="cover"
|
size={16}
|
||||||
muted
|
color="#fff"
|
||||||
paused={paused}
|
/>
|
||||||
repeat
|
</TouchableOpacity>
|
||||||
controls={false}
|
|
||||||
onReadyForDisplay={handleVideoReady}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
@@ -188,6 +207,18 @@ const styles = StyleSheet.create({
|
|||||||
zIndex: 2,
|
zIndex: 2,
|
||||||
},
|
},
|
||||||
durationText: { color: "#fff", fontSize: 10 },
|
durationText: { color: "#fff", fontSize: 10 },
|
||||||
|
muteBtn: {
|
||||||
|
position: "absolute",
|
||||||
|
top: 8,
|
||||||
|
right: 8,
|
||||||
|
backgroundColor: "rgba(0,0,0,0.5)",
|
||||||
|
borderRadius: 14,
|
||||||
|
width: 28,
|
||||||
|
height: 28,
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
zIndex: 3,
|
||||||
|
},
|
||||||
info: { padding: 8 },
|
info: { padding: 8 },
|
||||||
title: { fontSize: 14, color: "#212121", lineHeight: 18, marginBottom: 4 },
|
title: { fontSize: 14, color: "#212121", lineHeight: 18, marginBottom: 4 },
|
||||||
meta: {
|
meta: {
|
||||||
|
|||||||
@@ -190,6 +190,7 @@ export function NativeVideoPlayer({
|
|||||||
|
|
||||||
const [showQuality, setShowQuality] = useState(false);
|
const [showQuality, setShowQuality] = useState(false);
|
||||||
|
|
||||||
|
const [buffered, setBuffered] = useState(0);
|
||||||
const [isSeeking, setIsSeeking] = useState(false);
|
const [isSeeking, setIsSeeking] = useState(false);
|
||||||
const isSeekingRef = useRef(false);
|
const isSeekingRef = useRef(false);
|
||||||
const [touchX, setTouchX] = useState<number | null>(null);
|
const [touchX, setTouchX] = useState<number | null>(null);
|
||||||
@@ -332,6 +333,7 @@ export function NativeVideoPlayer({
|
|||||||
const touchRatio =
|
const touchRatio =
|
||||||
touchX !== null ? clamp(touchX / barWidthRef.current, 0, 1) : null;
|
touchX !== null ? clamp(touchX / barWidthRef.current, 0, 1) : null;
|
||||||
const progressRatio = duration > 0 ? clamp(currentTime / duration, 0, 1) : 0;
|
const progressRatio = duration > 0 ? clamp(currentTime / duration, 0, 1) : 0;
|
||||||
|
const bufferedRatio = duration > 0 ? clamp(buffered / duration, 0, 1) : 0;
|
||||||
|
|
||||||
const THUMB_DISPLAY_W = 120; // scaled display width
|
const THUMB_DISPLAY_W = 120; // scaled display width
|
||||||
|
|
||||||
@@ -419,9 +421,10 @@ export function NativeVideoPlayer({
|
|||||||
resizeMode="contain"
|
resizeMode="contain"
|
||||||
controls={false}
|
controls={false}
|
||||||
paused={paused}
|
paused={paused}
|
||||||
onProgress={({ currentTime: ct, seekableDuration: dur }) => {
|
onProgress={({ currentTime: ct, seekableDuration: dur, playableDuration: buf }) => {
|
||||||
setCurrentTime(ct);
|
setCurrentTime(ct);
|
||||||
if (dur > 0) setDuration(dur);
|
if (dur > 0) setDuration(dur);
|
||||||
|
setBuffered(buf);
|
||||||
onTimeUpdate?.(ct);
|
onTimeUpdate?.(ct);
|
||||||
}}
|
}}
|
||||||
onLoad={() => {
|
onLoad={() => {
|
||||||
@@ -498,16 +501,18 @@ export function NativeVideoPlayer({
|
|||||||
{...panResponder.panHandlers}
|
{...panResponder.panHandlers}
|
||||||
>
|
>
|
||||||
<View style={styles.track}>
|
<View style={styles.track}>
|
||||||
|
{/* Buffered: lighter white overlay on top of base */}
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
styles.seg,
|
styles.trackLayer,
|
||||||
{ flex: 1, backgroundColor: "#00AEEC" },
|
{ width: `${bufferedRatio * 100}%` as any, backgroundColor: "rgba(255,255,255,0.35)" },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
{/* Played: accent color on top */}
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
styles.playedOverlay,
|
styles.trackLayer,
|
||||||
{ width: `${progressRatio * 100}%` as any },
|
{ width: `${progressRatio * 100}%` as any, backgroundColor: "#00AEEC" },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
@@ -672,18 +677,15 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
track: {
|
track: {
|
||||||
height: BAR_H,
|
height: BAR_H,
|
||||||
flexDirection: "row",
|
|
||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
backgroundColor: "rgba(255,255,255,0.3)",
|
backgroundColor: "rgba(255,255,255,0.2)",
|
||||||
},
|
},
|
||||||
seg: { height: BAR_H },
|
trackLayer: {
|
||||||
playedOverlay: {
|
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
height: BAR_H,
|
height: BAR_H,
|
||||||
backgroundColor: "rgba(255,255,255,0.3)",
|
|
||||||
},
|
},
|
||||||
ball: {
|
ball: {
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
|
|||||||
@@ -27,11 +27,19 @@ export function toListRows(videos: VideoItem[]): ListRow[] {
|
|||||||
const rows: ListRow[] = [];
|
const rows: ListRow[] = [];
|
||||||
for (let start = 0; start < videos.length; start += PAGE) {
|
for (let start = 0; start < videos.length; start += PAGE) {
|
||||||
const chunk = videos.slice(start, start + PAGE);
|
const chunk = videos.slice(start, start + PAGE);
|
||||||
const body = chunk.slice(0, chunk.length - 1);
|
// Pick the video with the highest view count as the BigRow
|
||||||
for (let i = 0; i < body.length; i += 2) {
|
let bigIdx = 0;
|
||||||
rows.push({ type: 'pair', left: body[i], right: body[i + 1] ?? null });
|
let maxView = chunk[0].stat?.view ?? 0;
|
||||||
|
for (let i = 1; i < chunk.length; i++) {
|
||||||
|
const v = chunk[i].stat?.view ?? 0;
|
||||||
|
if (v > maxView) { maxView = v; bigIdx = i; }
|
||||||
}
|
}
|
||||||
rows.push({ type: 'big', item: chunk[chunk.length - 1] });
|
const bigItem = chunk[bigIdx];
|
||||||
|
const rest = chunk.filter((_, i) => i !== bigIdx);
|
||||||
|
for (let i = 0; i < rest.length; i += 2) {
|
||||||
|
rows.push({ type: 'pair', left: rest[i], right: rest[i + 1] ?? null });
|
||||||
|
}
|
||||||
|
rows.push({ type: 'big', item: bigItem });
|
||||||
}
|
}
|
||||||
|
|
||||||
return rows;
|
return rows;
|
||||||
|
|||||||
Reference in New Issue
Block a user