more fixes web + mobile
This commit is contained in:
@@ -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={{
|
||||
|
||||
Reference in New Issue
Block a user