瀏覽代碼

[cast] Fix live photo handling, precache (#1465)

- Fix live photo cast (previously these were not being rendered because
of an incorrect conditional)
- Precache

There is still an initial delay where the missing thumbnail is shown on
switching to the next image on first load. But anyways, these are
improvements, can tackle that later.
Manav Rathi 1 年之前
父節點
當前提交
486044ad51

+ 18 - 49
web/apps/cast/src/components/PhotoAuditorium.tsx

@@ -1,50 +1,24 @@
-import { SlideshowContext } from "pages/slideshow";
-import { useContext, useEffect, useState } from "react";
+import { useEffect } from "react";
 
 
-export default function PhotoAuditorium({
-    url,
-    nextSlideUrl,
-}: {
+interface PhotoAuditoriumProps {
     url: string;
     url: string;
     nextSlideUrl: string;
     nextSlideUrl: string;
-}) {
-    const { showNextSlide } = useContext(SlideshowContext);
-
-    const [showPreloadedNextSlide, setShowPreloadedNextSlide] = useState(false);
-    const [nextSlidePrerendered, setNextSlidePrerendered] = useState(false);
-    const [prerenderTime, setPrerenderTime] = useState<number | null>(null);
-
+    showNextSlide: () => void;
+}
+export const PhotoAuditorium: React.FC<PhotoAuditoriumProps> = ({
+    url,
+    nextSlideUrl,
+    showNextSlide,
+}) => {
     useEffect(() => {
     useEffect(() => {
-        let timeout: NodeJS.Timeout;
-        let timeout2: NodeJS.Timeout;
-
-        if (nextSlidePrerendered) {
-            const elapsedTime = prerenderTime ? Date.now() - prerenderTime : 0;
-            const delayTime = Math.max(10000 - elapsedTime, 0);
-
-            if (elapsedTime >= 10000) {
-                setShowPreloadedNextSlide(true);
-            } else {
-                timeout = setTimeout(() => {
-                    setShowPreloadedNextSlide(true);
-                }, delayTime);
-            }
-
-            if (showNextSlide) {
-                timeout2 = setTimeout(() => {
-                    showNextSlide();
-                    setNextSlidePrerendered(false);
-                    setPrerenderTime(null);
-                    setShowPreloadedNextSlide(false);
-                }, delayTime);
-            }
-        }
+        const timeoutId = window.setTimeout(() => {
+            showNextSlide();
+        }, 10000);
 
 
         return () => {
         return () => {
-            if (timeout) clearTimeout(timeout);
-            if (timeout2) clearTimeout(timeout2);
+            if (timeoutId) clearTimeout(timeoutId);
         };
         };
-    }, [nextSlidePrerendered, showNextSlide, prerenderTime]);
+    }, [showNextSlide]);
 
 
     return (
     return (
         <div
         <div
@@ -70,26 +44,21 @@ export default function PhotoAuditorium({
                 }}
                 }}
             >
             >
                 <img
                 <img
-                    src={url}
+                    src={nextSlideUrl}
                     style={{
                     style={{
                         maxWidth: "100%",
                         maxWidth: "100%",
                         maxHeight: "100%",
                         maxHeight: "100%",
-                        display: showPreloadedNextSlide ? "none" : "block",
+                        display: "none",
                     }}
                     }}
                 />
                 />
                 <img
                 <img
-                    src={nextSlideUrl}
+                    src={url}
                     style={{
                     style={{
                         maxWidth: "100%",
                         maxWidth: "100%",
                         maxHeight: "100%",
                         maxHeight: "100%",
-                        display: showPreloadedNextSlide ? "block" : "none",
-                    }}
-                    onLoad={() => {
-                        setNextSlidePrerendered(true);
-                        setPrerenderTime(Date.now());
                     }}
                     }}
                 />
                 />
             </div>
             </div>
         </div>
         </div>
     );
     );
-}
+};

+ 0 - 95
web/apps/cast/src/components/Theatre/PhotoAuditorium.tsx

@@ -1,95 +0,0 @@
-import { SlideshowContext } from "pages/slideshow";
-import { useContext, useEffect, useState } from "react";
-
-export default function PhotoAuditorium({
-    url,
-    nextSlideUrl,
-}: {
-    url: string;
-    nextSlideUrl: string;
-}) {
-    const { showNextSlide } = useContext(SlideshowContext);
-
-    const [showPreloadedNextSlide, setShowPreloadedNextSlide] = useState(false);
-    const [nextSlidePrerendered, setNextSlidePrerendered] = useState(false);
-    const [prerenderTime, setPrerenderTime] = useState<number | null>(null);
-
-    useEffect(() => {
-        let timeout: NodeJS.Timeout;
-        let timeout2: NodeJS.Timeout;
-
-        if (nextSlidePrerendered) {
-            const elapsedTime = prerenderTime ? Date.now() - prerenderTime : 0;
-            const delayTime = Math.max(10000 - elapsedTime, 0);
-
-            if (elapsedTime >= 10000) {
-                setShowPreloadedNextSlide(true);
-            } else {
-                timeout = setTimeout(() => {
-                    setShowPreloadedNextSlide(true);
-                }, delayTime);
-            }
-
-            if (showNextSlide) {
-                timeout2 = setTimeout(() => {
-                    showNextSlide();
-                    setNextSlidePrerendered(false);
-                    setPrerenderTime(null);
-                    setShowPreloadedNextSlide(false);
-                }, delayTime);
-            }
-        }
-
-        return () => {
-            if (timeout) clearTimeout(timeout);
-            if (timeout2) clearTimeout(timeout2);
-        };
-    }, [nextSlidePrerendered, showNextSlide, prerenderTime]);
-
-    return (
-        <div
-            style={{
-                width: "100vw",
-                height: "100vh",
-                backgroundImage: `url(${url})`,
-                backgroundSize: "cover",
-                backgroundPosition: "center",
-                backgroundRepeat: "no-repeat",
-                backgroundBlendMode: "multiply",
-                backgroundColor: "rgba(0, 0, 0, 0.5)",
-            }}
-        >
-            <div
-                style={{
-                    height: "100%",
-                    width: "100%",
-                    display: "flex",
-                    justifyContent: "center",
-                    alignItems: "center",
-                    backdropFilter: "blur(10px)",
-                }}
-            >
-                <img
-                    src={url}
-                    style={{
-                        maxWidth: "100%",
-                        maxHeight: "100%",
-                        display: showPreloadedNextSlide ? "none" : "block",
-                    }}
-                />
-                <img
-                    src={nextSlideUrl}
-                    style={{
-                        maxWidth: "100%",
-                        maxHeight: "100%",
-                        display: showPreloadedNextSlide ? "block" : "none",
-                    }}
-                    onLoad={() => {
-                        setNextSlidePrerendered(true);
-                        setPrerenderTime(Date.now());
-                    }}
-                />
-            </div>
-        </div>
-    );
-}

+ 0 - 55
web/apps/cast/src/components/Theatre/VideoAuditorium.tsx

@@ -1,55 +0,0 @@
-import mime from "mime-types";
-import { SlideshowContext } from "pages/slideshow";
-import { useContext, useEffect, useRef } from "react";
-
-export default function VideoAuditorium({
-    name,
-    url,
-}: {
-    name: string;
-    url: string;
-}) {
-    const { showNextSlide } = useContext(SlideshowContext);
-
-    const videoRef = useRef<HTMLVideoElement>(null);
-
-    useEffect(() => {
-        attemptPlay();
-    }, [url, videoRef]);
-
-    const attemptPlay = async () => {
-        if (videoRef.current) {
-            try {
-                await videoRef.current.play();
-            } catch {
-                showNextSlide();
-            }
-        }
-    };
-
-    return (
-        <div
-            style={{
-                width: "100vw",
-                height: "100vh",
-                display: "flex",
-                justifyContent: "center",
-                alignItems: "center",
-            }}
-        >
-            <video
-                ref={videoRef}
-                autoPlay
-                controls
-                style={{
-                    maxWidth: "100vw",
-                    maxHeight: "100vh",
-                }}
-                onError={showNextSlide}
-                onEnded={showNextSlide}
-            >
-                <source src={url} type={mime.lookup(name)} />
-            </video>
-        </div>
-    );
-}

+ 0 - 30
web/apps/cast/src/components/Theatre/index.tsx

@@ -1,30 +0,0 @@
-import { FILE_TYPE } from "constants/file";
-import PhotoAuditorium from "./PhotoAuditorium";
-// import VideoAuditorium from './VideoAuditorium';
-
-interface fileProp {
-    fileName: string;
-    fileURL: string;
-    type: FILE_TYPE;
-}
-
-interface IProps {
-    file1: fileProp;
-    file2: fileProp;
-}
-
-export default function Theatre(props: IProps) {
-    switch (props.file1.type && props.file2.type) {
-        case FILE_TYPE.IMAGE:
-            return (
-                <PhotoAuditorium
-                    url={props.file1.fileURL}
-                    nextSlideUrl={props.file2.fileURL}
-                />
-            );
-        // case FILE_TYPE.VIDEO:
-        //     return (
-        //         <VideoAuditorium name={props.fileName} url={props.fileURL} />
-        //     );
-    }
-}

+ 46 - 83
web/apps/cast/src/pages/slideshow.tsx

@@ -1,9 +1,9 @@
 import log from "@/next/log";
 import log from "@/next/log";
 import PairedSuccessfullyOverlay from "components/PairedSuccessfullyOverlay";
 import PairedSuccessfullyOverlay from "components/PairedSuccessfullyOverlay";
-import Theatre from "components/Theatre";
+import { PhotoAuditorium } from "components/PhotoAuditorium";
 import { FILE_TYPE } from "constants/file";
 import { FILE_TYPE } from "constants/file";
 import { useRouter } from "next/router";
 import { useRouter } from "next/router";
-import { createContext, useEffect, useState } from "react";
+import { useEffect, useState } from "react";
 import {
 import {
     getCastCollection,
     getCastCollection,
     getLocalFiles,
     getLocalFiles,
@@ -13,25 +13,20 @@ import { Collection } from "types/collection";
 import { EnteFile } from "types/file";
 import { EnteFile } from "types/file";
 import { getPreviewableImage, isRawFileFromFileName } from "utils/file";
 import { getPreviewableImage, isRawFileFromFileName } from "utils/file";
 
 
-export const SlideshowContext = createContext<{
-    showNextSlide: () => void;
-}>(null);
-
 const renderableFileURLCache = new Map<number, string>();
 const renderableFileURLCache = new Map<number, string>();
 
 
 export default function Slideshow() {
 export default function Slideshow() {
-    const [collectionFiles, setCollectionFiles] = useState<EnteFile[]>([]);
-
-    const [currentFile, setCurrentFile] = useState<EnteFile | undefined>(
-        undefined,
-    );
-    const [nextFile, setNextFile] = useState<EnteFile | undefined>(undefined);
-
     const [loading, setLoading] = useState(true);
     const [loading, setLoading] = useState(true);
     const [castToken, setCastToken] = useState<string>("");
     const [castToken, setCastToken] = useState<string>("");
     const [castCollection, setCastCollection] = useState<
     const [castCollection, setCastCollection] = useState<
         Collection | undefined
         Collection | undefined
-    >(undefined);
+    >();
+    const [collectionFiles, setCollectionFiles] = useState<EnteFile[]>([]);
+    const [currentFileId, setCurrentFileId] = useState<number | undefined>();
+    const [currentFileURL, setCurrentFileURL] = useState<string | undefined>();
+    const [nextFileURL, setNextFileURL] = useState<string | undefined>();
+
+    const router = useRouter();
 
 
     const syncCastFiles = async (token: string) => {
     const syncCastFiles = async (token: string) => {
         try {
         try {
@@ -72,29 +67,16 @@ export default function Slideshow() {
 
 
     const isFileEligibleForCast = (file: EnteFile) => {
     const isFileEligibleForCast = (file: EnteFile) => {
         const fileType = file.metadata.fileType;
         const fileType = file.metadata.fileType;
-        if (fileType !== FILE_TYPE.IMAGE && fileType !== FILE_TYPE.LIVE_PHOTO) {
+        if (fileType !== FILE_TYPE.IMAGE && fileType !== FILE_TYPE.LIVE_PHOTO)
             return false;
             return false;
-        }
 
 
-        const fileSizeLimit = 100 * 1024 * 1024;
+        if (file.info.fileSize > 100 * 1024 * 1024) return false;
 
 
-        if (file.info.fileSize > fileSizeLimit) {
-            return false;
-        }
-
-        const name = file.metadata.title;
-
-        if (fileType === FILE_TYPE.IMAGE) {
-            if (isRawFileFromFileName(name)) {
-                return false;
-            }
-        }
+        if (isRawFileFromFileName(file.metadata.title)) return false;
 
 
         return true;
         return true;
     };
     };
 
 
-    const router = useRouter();
-
     useEffect(() => {
     useEffect(() => {
         try {
         try {
             const castToken = window.localStorage.getItem("castToken");
             const castToken = window.localStorage.getItem("castToken");
@@ -117,9 +99,9 @@ export default function Slideshow() {
         showNextSlide();
         showNextSlide();
     }, [collectionFiles]);
     }, [collectionFiles]);
 
 
-    const showNextSlide = () => {
+    const showNextSlide = async () => {
         const currentIndex = collectionFiles.findIndex(
         const currentIndex = collectionFiles.findIndex(
-            (file) => file.id === currentFile?.id,
+            (file) => file.id === currentFileId,
         );
         );
 
 
         const nextIndex = (currentIndex + 1) % collectionFiles.length;
         const nextIndex = (currentIndex + 1) % collectionFiles.length;
@@ -128,63 +110,44 @@ export default function Slideshow() {
         const nextFile = collectionFiles[nextIndex];
         const nextFile = collectionFiles[nextIndex];
         const nextNextFile = collectionFiles[nextNextIndex];
         const nextNextFile = collectionFiles[nextNextIndex];
 
 
-        setCurrentFile(nextFile);
-        setNextFile(nextNextFile);
-    };
-
-    const [renderableFileURL, setRenderableFileURL] = useState<string>("");
-
-    const getRenderableFileURL = async () => {
-        if (!currentFile) return;
-
-        const cacheValue = renderableFileURLCache.get(currentFile.id);
-        if (cacheValue) {
-            setRenderableFileURL(cacheValue);
-            setLoading(false);
-            return;
+        let nextURL = renderableFileURLCache.get(nextFile.id);
+        let nextNextURL = renderableFileURLCache.get(nextNextFile.id);
+
+        if (!nextURL) {
+            try {
+                const blob = await getPreviewableImage(nextFile, castToken);
+                const url = URL.createObjectURL(blob);
+                renderableFileURLCache.set(nextFile.id, url);
+                nextURL = url;
+            } catch (e) {
+                return;
+            }
         }
         }
 
 
-        try {
-            const blob = await getPreviewableImage(
-                currentFile as EnteFile,
-                castToken,
-            );
-
-            const url = URL.createObjectURL(blob);
-
-            renderableFileURLCache.set(currentFile?.id, url);
-
-            setRenderableFileURL(url);
-        } catch (e) {
-            return;
-        } finally {
-            setLoading(false);
+        if (!nextNextURL) {
+            try {
+                const blob = await getPreviewableImage(nextNextFile, castToken);
+                const url = URL.createObjectURL(blob);
+                renderableFileURLCache.set(nextNextFile.id, url);
+                nextNextURL = url;
+            } catch (e) {
+                return;
+            }
         }
         }
+
+        setLoading(false);
+        setCurrentFileId(nextFile.id);
+        setCurrentFileURL(nextURL);
+        setNextFileURL(nextNextURL);
     };
     };
 
 
-    useEffect(() => {
-        if (currentFile) {
-            getRenderableFileURL();
-        }
-    }, [currentFile]);
+    if (loading) return <PairedSuccessfullyOverlay />;
 
 
     return (
     return (
-        <>
-            <SlideshowContext.Provider value={{ showNextSlide }}>
-                <Theatre
-                    file1={{
-                        fileName: currentFile?.metadata.title,
-                        fileURL: renderableFileURL,
-                        type: currentFile?.metadata.fileType,
-                    }}
-                    file2={{
-                        fileName: nextFile?.metadata.title,
-                        fileURL: renderableFileURL,
-                        type: nextFile?.metadata.fileType,
-                    }}
-                />
-            </SlideshowContext.Provider>
-            {loading && <PairedSuccessfullyOverlay />}
-        </>
+        <PhotoAuditorium
+            url={currentFileURL}
+            nextSlideUrl={nextFileURL}
+            showNextSlide={showNextSlide}
+        />
     );
     );
 }
 }

+ 1 - 1
web/apps/photos/src/components/Collections/CollectionOptions/AlbumCastDialog.tsx

@@ -50,7 +50,7 @@ export default function AlbumCastDialog(props: Props) {
         setFieldError,
         setFieldError,
     ) => {
     ) => {
         try {
         try {
-            await doCast(value);
+            await doCast(value.trim());
             props.onHide();
             props.onHide();
         } catch (e) {
         } catch (e) {
             const error = e as Error;
             const error = e as Error;