more fixes web + mobile

This commit is contained in:
Thomas Demirdjian
2025-11-20 11:26:24 +01:00
parent fe9b2b9e8d
commit 70c94c97b4
32 changed files with 1214 additions and 226 deletions
+197 -8
View File
@@ -8,14 +8,21 @@ import React, {
useRef,
useState,
} from "react";
import { Image, Platform, Pressable, Text, View } from "react-native";
import {
ActivityIndicator,
Image,
Platform,
Pressable,
Text,
View,
} from "react-native";
import Carousel from "react-native-reanimated-carousel";
import { responsiveHeight } from "react-native-responsive-dimensions";
import { icons, img } from "../../assets";
import { openComments } from "../../components/bottomsheets/CommentsBottomSheet";
import KaraokeLyrics from "../../components/KaraokeLyrics";
import ProfilePicture from "../../components/ProfilePicture";
import { projectsRef, usersRef } from "../../config/firebase";
import firebase, { projectsRef, usersRef } from "../../config/firebase";
import useDataFromRef from "../../hooks/useDataFromRef";
import { Routes } from "../../navigation";
import { navigate } from "../../navigation/NavigationService";
@@ -36,14 +43,37 @@ import {
import { SheetManager } from "react-native-actions-sheet";
import { Feather } from "@expo/vector-icons";
const PLAYBACK_CODEC_TAG = "h264-v1";
const PlaybackItem = ({ item, isActive, userCache, getUserByUid }) => {
const { currentUID, followUser, unfollowUser } = useUser() || {};
const videoUrl = item?.playbackUrl || null;
const baseVideoUrl = item?.playbackUrl || null;
const [overrideVideoUrl, setOverrideVideoUrl] = useState(null);
const videoUrl = overrideVideoUrl || baseVideoUrl;
const initialLikedBy = getProjectLikes(item, LIKE_TARGET.PLAYBACK);
const [isLiked, setIsLiked] = useState(
currentUID ? initialLikedBy.includes(currentUID) : false
);
const [likesCount, setLikesCount] = useState(initialLikedBy.length);
const [repairState, setRepairState] = useState({
running: false,
error: null,
});
const repairAttemptedRef = useRef(false);
useEffect(() => {
setOverrideVideoUrl(null);
repairAttemptedRef.current = false;
setRepairState({ running: false, error: null });
}, [item?.id, baseVideoUrl]);
useEffect(() => {
logPlaybackEvent("state-change", {
baseVideoUrl: baseVideoUrl || null,
overrideVideoUrl,
repairState,
});
}, [baseVideoUrl, logPlaybackEvent, overrideVideoUrl, repairState]);
const commentsCount = useMemo(
() => Number(item?.commentsCount || 0),
@@ -122,31 +152,137 @@ const PlaybackItem = ({ item, isActive, userCache, getUserByUid }) => {
return "";
}, [item?.userName, owner?.artistName, owner?.displayName, owner?.userName]);
const needsCodecRepair =
Platform.OS === "ios" &&
!!baseVideoUrl &&
item?.playbackCompatibility?.codec !== PLAYBACK_CODEC_TAG;
const logPlaybackEvent = useCallback(
(event, extra = {}) => {
if (Platform.OS !== "ios") return;
try {
console.log("[Playbacks][iOS]", event, {
projectId: item?.id,
isActive,
hasVideoUrl: !!videoUrl,
hasOverride: !!overrideVideoUrl,
needsCodecRepair,
compatibility: item?.playbackCompatibility || null,
repairRunning: repairState.running,
repairError: repairState.error?.message || null,
...extra,
});
} catch (loggingError) {
// ignore logging errors
}
},
[
item?.id,
isActive,
needsCodecRepair,
overrideVideoUrl,
repairState.error?.message,
repairState.running,
videoUrl,
]
);
const handleRepair = useCallback(
async (force = false) => {
if ((!needsCodecRepair && !force) || !item?.id) {
logPlaybackEvent("repair-skip", { force });
return;
}
if (repairAttemptedRef.current) {
logPlaybackEvent("repair-skip-already-attempted", { force });
return;
}
repairAttemptedRef.current = true;
setRepairState({ running: true, error: null });
logPlaybackEvent("repair-start", { force });
try {
const callable =
firebase.functions().httpsCallable("upload-reencodePlayback");
const { data } = await callable({ projectId: item.id });
const nextUrl = data?.url || null;
if (nextUrl) {
setOverrideVideoUrl(nextUrl);
}
setRepairState({ running: false, error: null });
logPlaybackEvent("repair-success", { nextUrlPresent: !!nextUrl });
} catch (error) {
console.log("[Playbacks] reencode failed", {
projectId: item?.id,
message: error?.message || String(error || ""),
code: error?.code,
});
setRepairState({
running: false,
error:
error instanceof Error
? error
: new Error(String(error || "Playback repair failed")),
});
logPlaybackEvent("repair-failed", {
error: error?.message || String(error || ""),
code: error?.code,
});
}
},
[item?.id, logPlaybackEvent, needsCodecRepair]
);
useEffect(() => {
if (!isActive || !needsCodecRepair) return;
handleRepair();
}, [isActive, needsCodecRepair, handleRepair]);
const videoPlayer = useVideoPlayer(videoUrl || null, (p) => {
p.loop = false;
p.muted = false;
p.timeUpdateEventInterval = 0.2;
});
useEffect(() => {
if (Platform.OS !== "ios") return;
if (!videoPlayer?.addListener) return;
const sub = videoPlayer.addListener("error", (event) => {
console.log("[Playbacks] video error", {
projectId: item?.id,
error: event?.error || event,
});
logPlaybackEvent("video-error", { error: event?.error || event });
if (!repairAttemptedRef.current) {
handleRepair(true);
}
});
return () => {
try {
sub?.remove?.();
} catch (e) {}
};
}, [videoPlayer, handleRepair, item?.id]);
const shouldAutoPlay = isActive && !repairState.running;
useEffect(() => {
const toggle = async () => {
try {
if (isActive) {
if (shouldAutoPlay) {
try {
if (videoPlayer) videoPlayer.currentTime = 0;
} catch (e) {}
// Lancer quasi simultanément (éviter await pour limiter le décalage)
try {
if (videoPlayer) videoPlayer.play();
} catch (e) {}
} else {
if (videoPlayer?.playing) videoPlayer.pause();
} else if (videoPlayer?.playing) {
videoPlayer.pause();
}
} catch (e) {}
};
toggle();
}, [isActive, videoPlayer]);
}, [shouldAutoPlay, videoPlayer]);
useEffect(() => {
return () => {
@@ -232,6 +368,59 @@ const PlaybackItem = ({ item, isActive, userCache, getUserByUid }) => {
/>
)}
{Platform.OS === "ios" && repairState.running && (
<View
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
alignItems: "center",
justifyContent: "center",
backgroundColor: "#00000066",
}}
>
<ActivityIndicator color={Palette.white} />
<Text
style={{
marginTop: 10,
color: Palette.white,
fontFamily: FONT_FAMILY.InterMedium,
fontSize: 15,
}}
>
Optimisation vidéo iOS...
</Text>
</View>
)}
{Platform.OS === "ios" && repairState.error && (
<View
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
alignItems: "center",
justifyContent: "center",
paddingHorizontal: 20,
backgroundColor: "#00000099",
}}
>
<Text
style={{
color: Palette.white,
fontFamily: FONT_FAMILY.InterMedium,
textAlign: "center",
}}
>
Impossible de lire cette vidéo. Veuillez réessayer plus tard.
</Text>
</View>
)}
{/* Right side actions */}
<View
style={{