[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.
This commit is contained in:
commit
486044ad51
6 changed files with 72 additions and 320 deletions
|
@ -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
|
||||
|
@ -69,27 +43,22 @@ export default function PhotoAuditorium({
|
|||
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",
|
||||
display: "none",
|
||||
}}
|
||||
onLoad={() => {
|
||||
setNextSlidePrerendered(true);
|
||||
setPrerenderTime(Date.now());
|
||||
/>
|
||||
<img
|
||||
src={url}
|
||||
style={{
|
||||
maxWidth: "100%",
|
||||
maxHeight: "100%",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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} />
|
||||
// );
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
const [renderableFileURL, setRenderableFileURL] = useState<string>("");
|
||||
|
||||
const getRenderableFileURL = async () => {
|
||||
if (!currentFile) return;
|
||||
|
||||
const cacheValue = renderableFileURLCache.get(currentFile.id);
|
||||
if (cacheValue) {
|
||||
setRenderableFileURL(cacheValue);
|
||||
setLoading(false);
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Reference in a new issue