Different variations
This commit is contained in:
parent
7c0cc15a73
commit
72c99de344
5 changed files with 136 additions and 98 deletions
|
@ -1,55 +0,0 @@
|
|||
import { styled } from "@mui/material";
|
||||
|
||||
interface SlideViewProps {
|
||||
/** The URL of the image to show. */
|
||||
url: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the image at {@link url} in a full screen view.
|
||||
*
|
||||
* Also show {@link nextURL} in an hidden image view to prepare the browser for
|
||||
* an imminent transition to it.
|
||||
*/
|
||||
export const SlideView: React.FC<SlideViewProps> = ({ url }) => {
|
||||
return (
|
||||
<Container style={{ backgroundImage: `url(${url})` }}>
|
||||
<img src={url} decoding="sync" alt="" />
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
const Container = styled("div")`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-blend-mode: multiply;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
opacity: 0.2;
|
||||
|
||||
/* Smooth out the transition a bit.
|
||||
*
|
||||
* For the img itself, we set decoding="sync" to have it switch seamlessly.
|
||||
* But there does not seem to be a way of setting decoding sync for the
|
||||
* background image, and for large (multi-MB) images the background image
|
||||
* switch is still visually non-atomic.
|
||||
*
|
||||
* As a workaround, add a long transition so that the background image
|
||||
* transitions in a more "fade-to" manner. This effect might or might not be
|
||||
* visually the best though.
|
||||
*
|
||||
* Does not work in Firefox, but that's fine, this is only a slight tweak,
|
||||
* not a functional requirement.
|
||||
*/
|
||||
/* transition: all 2s; */
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* backdrop-filter: blur(10px); */
|
||||
object-fit: contain;
|
||||
}
|
||||
`;
|
|
@ -6,7 +6,7 @@ import { useRouter } from "next/router";
|
|||
import { useEffect, useState } from "react";
|
||||
import { readCastData, storeCastData } from "services/cast-data";
|
||||
import { getCastData, register } from "services/pair";
|
||||
import { advertiseOnChromecast } from "../services/cast-receiver";
|
||||
import { advertiseOnChromecast } from "../services/chromecast";
|
||||
|
||||
export default function Index() {
|
||||
const [publicKeyB64, setPublicKeyB64] = useState<string | undefined>();
|
||||
|
|
|
@ -2,10 +2,10 @@ import log from "@/next/log";
|
|||
import { ensure } from "@/utils/ensure";
|
||||
import { styled } from "@mui/material";
|
||||
import { FilledCircleCheck } from "components/FilledCircleCheck";
|
||||
import { SlideView } from "components/Slide";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { readCastData } from "services/cast-data";
|
||||
import { isChromecast } from "services/chromecast";
|
||||
import { imageURLGenerator } from "services/render";
|
||||
|
||||
export default function Slideshow() {
|
||||
|
@ -55,7 +55,11 @@ export default function Slideshow() {
|
|||
if (loading) return <PairingComplete />;
|
||||
if (isEmpty) return <NoItems />;
|
||||
|
||||
return <SlideView url={imageURL} />;
|
||||
return isChromecast() ? (
|
||||
<SlideViewChromecast url={imageURL} />
|
||||
) : (
|
||||
<SlideView url={imageURL} />
|
||||
);
|
||||
}
|
||||
|
||||
const PairingComplete: React.FC = () => {
|
||||
|
@ -97,3 +101,97 @@ const NoItems: React.FC = () => {
|
|||
</Message>
|
||||
);
|
||||
};
|
||||
|
||||
interface SlideViewProps {
|
||||
/** The URL of the image to show. */
|
||||
url: string;
|
||||
}
|
||||
|
||||
const SlideView: React.FC<SlideViewProps> = ({ url }) => {
|
||||
return (
|
||||
<SlideView_ style={{ backgroundImage: `url(${url})` }}>
|
||||
<img src={url} decoding="sync" alt="" />
|
||||
</SlideView_>
|
||||
);
|
||||
};
|
||||
|
||||
const SlideView_ = styled("div")`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-blend-mode: multiply;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
|
||||
/* Smooth out the transition a bit.
|
||||
*
|
||||
* For the img itself, we set decoding="sync" to have it switch seamlessly.
|
||||
* But there does not seem to be a way of setting decoding sync for the
|
||||
* background image, and for large (multi-MB) images the background image
|
||||
* switch is still visually non-atomic.
|
||||
*
|
||||
* As a workaround, add a long transition so that the background image
|
||||
* transitions in a more "fade-to" manner. This effect might or might not be
|
||||
* visually the best though.
|
||||
*
|
||||
* Does not work in Firefox, but that's fine, this is only a slight tweak,
|
||||
* not a functional requirement.
|
||||
*/
|
||||
transition: all 2s;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
backdrop-filter: blur(10px);
|
||||
object-fit: contain;
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* Variant of {@link SlideView} for use when we're running on Chromecast.
|
||||
*
|
||||
* Chromecast devices have trouble with
|
||||
*
|
||||
* backdrop-filter: blur(10px);
|
||||
*
|
||||
* So emulate a cheaper approximation for use on Chromecast.
|
||||
*/
|
||||
const SlideViewChromecast: React.FC<SlideViewProps> = ({ url }) => {
|
||||
return (
|
||||
<SlideViewChromecast_>
|
||||
<img className="svc-bg" src={url} alt="" />
|
||||
<img className="svc-content" src={url} decoding="sync" alt="" />
|
||||
</SlideViewChromecast_>
|
||||
);
|
||||
};
|
||||
|
||||
const SlideViewChromecast_ = styled("div")`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
/* We can't set opacity of background-image, so use a wrapper */
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
img.svg-bg {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
|
||||
opacity: 0.2;
|
||||
background-blend-mode: multiply;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
img.svc-content {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -31,6 +31,10 @@ class CastReceiver {
|
|||
* always happens in response to a message handler.
|
||||
*/
|
||||
haveStarted = false;
|
||||
/**
|
||||
* Cached result of the isChromecast test.
|
||||
*/
|
||||
isChromecast: boolean | undefined;
|
||||
/**
|
||||
* A callback to invoke to get the pairing code when we get a new incoming
|
||||
* pairing request.
|
||||
|
@ -201,3 +205,23 @@ const advertiseCode = (cast: Cast) => {
|
|||
// Start listening for Chromecast connections.
|
||||
context.start(options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return true if we're running on a Chromecast device.
|
||||
*
|
||||
* This allows changing our app's behaviour when we're running on Chromecast.
|
||||
* Such checks are needed because during our testing we found that in practice,
|
||||
* some processing is too heavy for Chromecast hardware (we tested with a 2nd
|
||||
* gen device, this might not be true for newer variants).
|
||||
*
|
||||
* This variable is lazily updated when we enter {@link renderableImageURLs}. It
|
||||
* is kept at the top level to avoid passing it around.
|
||||
*/
|
||||
export const isChromecast = () => {
|
||||
let result = castReceiver.isChromecast;
|
||||
if (result === undefined) {
|
||||
result = window.navigator.userAgent.includes("CrKey");
|
||||
castReceiver.isChromecast = result;
|
||||
}
|
||||
return result;
|
||||
};
|
|
@ -27,31 +27,7 @@ import {
|
|||
FileMagicMetadata,
|
||||
FilePublicMagicMetadata,
|
||||
} from "types/file";
|
||||
|
||||
/**
|
||||
* Change the behaviour when we're running on Chromecast.
|
||||
*
|
||||
* The Chromecast device fails to load the images if we give it too large
|
||||
* images. The documentation states:
|
||||
*
|
||||
* > Images have a display size limitation of 720p (1280x720). Images should be
|
||||
* > optimized to 1280x720 or less to avoid scaling down on the receiver device.
|
||||
* >
|
||||
* > https://developers.google.com/cast/docs/media
|
||||
*
|
||||
* When testing with Chromecast device (2nd gen, this might not be true for
|
||||
* newer variants), in practice we found that even this is iffy, likely because
|
||||
* in our case we also need to decrypt the E2EE data.
|
||||
*
|
||||
* So we have different codepaths when running on a Chromecast hardware.
|
||||
*
|
||||
* Also, to detect if we're running on a Chromecast, a user-agent check is the
|
||||
* only way. See: https://issuetracker.google.com/issues/36189456.
|
||||
*
|
||||
* This variable is lazily updated when we enter {@link renderableImageURLs}. It
|
||||
* is kept at the top level to avoid passing it around.
|
||||
*/
|
||||
// let isChromecast = false;
|
||||
import { isChromecast } from "./chromecast";
|
||||
|
||||
/**
|
||||
* If we're using HEIC conversion, then this variable caches the comlink web
|
||||
|
@ -119,8 +95,6 @@ export const imageURLGenerator = async function* (castData: CastData) {
|
|||
*/
|
||||
let consecutiveFailures = 0;
|
||||
|
||||
// isChromecast = window.navigator.userAgent.includes("CrKey");
|
||||
|
||||
while (true) {
|
||||
const encryptedFiles = shuffled(
|
||||
await getEncryptedCollectionFiles(castToken),
|
||||
|
@ -166,8 +140,8 @@ export const imageURLGenerator = async function* (castData: CastData) {
|
|||
//
|
||||
// The last to last element is the one that was shown prior to that,
|
||||
// and now can be safely revoked.
|
||||
// if (previousURLs.length > 1)
|
||||
// URL.revokeObjectURL(previousURLs.shift());
|
||||
if (previousURLs.length > 1)
|
||||
URL.revokeObjectURL(previousURLs.shift());
|
||||
|
||||
previousURLs.push(url);
|
||||
|
||||
|
@ -318,7 +292,7 @@ export const heicToJPEG = async (heicBlob: Blob) => {
|
|||
*/
|
||||
const createRenderableURL = async (castToken: string, file: EnteFile) => {
|
||||
const imageBlob = await renderableImageBlob(castToken, file);
|
||||
const resizedBlob = needsResize() ? await resize(imageBlob) : imageBlob;
|
||||
const resizedBlob = needsResize(file) ? await resize(imageBlob) : imageBlob;
|
||||
return URL.createObjectURL(resizedBlob);
|
||||
};
|
||||
|
||||
|
@ -353,7 +327,8 @@ const downloadFile = async (castToken: string, file: EnteFile) => {
|
|||
if (!isImageOrLivePhoto(file))
|
||||
throw new Error("Can only cast images and live photos");
|
||||
|
||||
const shouldUseThumbnail = true;
|
||||
// TODO(MR):
|
||||
const shouldUseThumbnail = false;
|
||||
|
||||
const url = shouldUseThumbnail
|
||||
? getCastThumbnailURL(file.id)
|
||||
|
@ -384,11 +359,6 @@ const downloadFile = async (castToken: string, file: EnteFile) => {
|
|||
/**
|
||||
* [Note: Chromecast media size limits]
|
||||
*
|
||||
* The Chromecast device fails to load the images if we give it too large
|
||||
* images. This was tested in practice with a 2nd Gen Chromecast.
|
||||
*
|
||||
* The documentation also states:
|
||||
*
|
||||
* > Images have a display size limitation of 720p (1280x720). Images should be
|
||||
* > optimized to 1280x720 or less to avoid scaling down on the receiver device.
|
||||
* >
|
||||
|
@ -397,15 +367,16 @@ const downloadFile = async (castToken: string, file: EnteFile) => {
|
|||
* So if the size of the image we're wanting to show is more than these limits,
|
||||
* resize it down to a JPEG whose size is clamped to these limits.
|
||||
*/
|
||||
const needsResize = () => {
|
||||
//file: EnteFile) => {
|
||||
return false; /*
|
||||
const needsResize = (file: EnteFile) => {
|
||||
// Resize only when running on Chromecast devices.
|
||||
if (!isChromecast()) return false;
|
||||
|
||||
const w = file.pubMagicMetadata?.data?.w;
|
||||
const h = file.pubMagicMetadata?.data?.h;
|
||||
// If we don't have the size, always resize to be on the safer side.
|
||||
if (!w || !h) return true;
|
||||
// Otherwise resize if any of the dimensions is outside the recommendation.
|
||||
return Math.max(w, h) > 1280 || Math.min(w, h) > 720;*/
|
||||
return Math.max(w, h) > 1280 || Math.min(w, h) > 720;
|
||||
};
|
||||
|
||||
const resize = async (blob: Blob): Promise<Blob> => {
|
||||
|
|
Loading…
Reference in a new issue