Browse Source

[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 year ago
parent
commit
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;
     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(() => {
-        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 () => {
-            if (timeout) clearTimeout(timeout);
-            if (timeout2) clearTimeout(timeout2);
+            if (timeoutId) clearTimeout(timeoutId);
         };
-    }, [nextSlidePrerendered, showNextSlide, prerenderTime]);
+    }, [showNextSlide]);
 
     return (
         <div
@@ -70,26 +44,21 @@ export default function PhotoAuditorium({
                 }}
             >
                 <img
-                    src={url}
+                    src={nextSlideUrl}
                     style={{
                         maxWidth: "100%",
                         maxHeight: "100%",
-                        display: showPreloadedNextSlide ? "none" : "block",
+                        display: "none",
                     }}
                 />
                 <img
-                    src={nextSlideUrl}
+                    src={url}
                     style={{
                         maxWidth: "100%",
                         maxHeight: "100%",
-                        display: showPreloadedNextSlide ? "block" : "none",
-                    }}
-                    onLoad={() => {
-                        setNextSlidePrerendered(true);
-                        setPrerenderTime(Date.now());
                     }}
                 />
             </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 PairedSuccessfullyOverlay from "components/PairedSuccessfullyOverlay";
-import Theatre from "components/Theatre";
+import { PhotoAuditorium } from "components/PhotoAuditorium";
 import { FILE_TYPE } from "constants/file";
 import { useRouter } from "next/router";
-import { createContext, useEffect, useState } from "react";
+import { useEffect, useState } from "react";
 import {
     getCastCollection,
     getLocalFiles,
@@ -13,25 +13,20 @@ import { Collection } from "types/collection";
 import { EnteFile } from "types/file";
 import { getPreviewableImage, isRawFileFromFileName } from "utils/file";
 
-export const SlideshowContext = createContext<{
-    showNextSlide: () => void;
-}>(null);
-
 const renderableFileURLCache = new Map<number, string>();
 
 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 [castToken, setCastToken] = useState<string>("");
     const [castCollection, setCastCollection] = useState<
         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) => {
         try {
@@ -72,29 +67,16 @@ export default function Slideshow() {
 
     const isFileEligibleForCast = (file: EnteFile) => {
         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;
-        }
 
-        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;
     };
 
-    const router = useRouter();
-
     useEffect(() => {
         try {
             const castToken = window.localStorage.getItem("castToken");
@@ -117,9 +99,9 @@ export default function Slideshow() {
         showNextSlide();
     }, [collectionFiles]);
 
-    const showNextSlide = () => {
+    const showNextSlide = async () => {
         const currentIndex = collectionFiles.findIndex(
-            (file) => file.id === currentFile?.id,
+            (file) => file.id === currentFileId,
         );
 
         const nextIndex = (currentIndex + 1) % collectionFiles.length;
@@ -128,63 +110,44 @@ export default function Slideshow() {
         const nextFile = collectionFiles[nextIndex];
         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 (
-        <>
-            <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,
     ) => {
         try {
-            await doCast(value);
+            await doCast(value.trim());
             props.onHide();
         } catch (e) {
             const error = e as Error;