This commit is contained in:
Manav Rathi
2025-05-13 13:14:14 +05:30
parent be2665f57f
commit 0284287c9c
3 changed files with 127 additions and 4 deletions

View File

@@ -61,6 +61,10 @@ import log from "ente-base/log";
import { savedLogs } from "ente-base/log-web";
import { customAPIHost } from "ente-base/origins";
import { downloadString } from "ente-base/utils/web";
import {
isHLSGenerationSupported,
toggleHLSGeneration,
} from "ente-gallery/services/video";
import { DeleteAccount } from "ente-new/photos/components/DeleteAccount";
import { DropdownInput } from "ente-new/photos/components/DropdownInput";
import { MLSettings } from "ente-new/photos/components/sidebar/MLSettings";
@@ -71,6 +75,7 @@ import {
} from "ente-new/photos/components/utils/dialog";
import { downloadAppDialogAttributes } from "ente-new/photos/components/utils/download";
import {
useHLSGenerationStatusSnapshot,
useSettingsSnapshot,
useUserDetailsSnapshot,
} from "ente-new/photos/components/utils/use-snapshot";
@@ -769,6 +774,9 @@ const Preferences: React.FC<NestedSidebarDrawerVisibilityProps> = ({
const { show: showMLSettings, props: mlSettingsVisibilityProps } =
useModalVisibility();
const hlsGenStatusSnapshot = useHLSGenerationStatusSnapshot();
const isHLSGenerationEnabled = !!hlsGenStatusSnapshot?.enabled;
useEffect(() => {
if (open) void syncSettings();
}, [open]);
@@ -799,6 +807,18 @@ const Preferences: React.FC<NestedSidebarDrawerVisibilityProps> = ({
/>
</RowButtonGroup>
)}
{isHLSGenerationSupported() && (
// TODO(HLS): Visual look
<Stack sx={{ px: "16px", py: "20px" }}>
<RowButtonGroup>
<RowSwitch
label={t("enabled")}
checked={isHLSGenerationEnabled}
onClick={toggleHLSGeneration}
/>
</RowButtonGroup>
</Stack>
)}
<RowButton
endIcon={<ChevronRightIcon />}
label={t("map")}

View File

@@ -49,6 +49,10 @@ import {
type TimestampedFileSystemUploadItem,
} from "./upload";
export type HLSGenerationStatus =
| { enabled: false }
| { enabled: true; estimatedPendingCount?: number };
interface VideoProcessingQueueItem {
/**
* The {@link EnteFile} (guaranteed to be of {@link FileType.video}) whose
@@ -75,6 +79,20 @@ const idleWaitMax = idleWaitInitial * 2 ** 6; /* 640 sec */
* This entire object will be reset on logout.
*/
class VideoState {
/**
* `true` if the generation of HLS streams has been enabled on this client.
*/
isHLSGenerationEnabled = false;
/**
* Subscriptions to {@link HLSGenerationStatus} updates attached using
* {@link hlsGenerationStatusSubscribe}.
*/
hlsGenerationStatusListeners: (() => void)[] = [];
/**
* Snapshot of the {@link HLSGenerationStatus} returned by the
* {@link hlsGenerationStatusSnapshot} function.
*/
hlsGenerationStatusSnapshot: HLSGenerationStatus | undefined;
/**
* Queue of recently uploaded items waiting to be processed.
*/
@@ -129,6 +147,77 @@ export const resetVideoState = () => {
_state = new VideoState();
};
/**
* A function that can be used to subscribe to updates in the HLS generation
* settings and status.
*
* See: [Note: Snapshots and useSyncExternalStore].
*/
export const hlsGenerationStatusSubscribe = (
onChange: () => void,
): (() => void) => {
_state.hlsGenerationStatusListeners.push(onChange);
return () => {
_state.hlsGenerationStatusListeners =
_state.hlsGenerationStatusListeners.filter((l) => l != onChange);
};
};
/**
* Return the last know, cached {@link HLSGenerationStatus}.
*
* See also {@link hlsGenerationStatusSubscribe}.
*
* A return value of `undefined` indicates that the HLS generation subsystem has
* not been initialized yet.
*/
export const hlsGenerationStatusSnapshot = ():
| HLSGenerationStatus
| undefined => _state.hlsGenerationStatusSnapshot;
const setHLSGenerationStatusSnapshot = (snapshot: HLSGenerationStatus) => {
_state.hlsGenerationStatusSnapshot = snapshot;
_state.hlsGenerationStatusListeners.forEach((l) => l());
};
/**
* Return `true` if this client is capable of generating HLS streams for the
* uploaded videos.
*
* This function implementation is written expecting to be called many times
* (e.g. during UI rendering).
*/
export const isHLSGenerationSupported = () =>
// Keep this check fast, we get called many times.
isDesktop &&
// TODO(HLS):
process.env.NEXT_PUBLIC_ENTE_WIP_VIDEO_STREAMING &&
settingsSnapshot().isInternalUser;
/**
* Enable or disable (toggle) the HLS generation on this client.
*
* When HLS generation is enabled, this client will process videos to generate a
* streamable variant of them.
*
* It can only be enabled when
*/
export const toggleHLSGeneration = () => {
if (!isHLSGenerationSupported()) {
assertionFailed();
return;
}
const enabled = !_state.isHLSGenerationEnabled;
// Right now we only set the enabled setting. The estimated count status
// will get filled in when we tick.
setHLSGenerationStatusSnapshot({ enabled });
// Wake up the processor if needed.
if (enabled) tickNow();
};
export interface HLSPlaylistData {
/** A data URL to a HLS playlist that streams the video. */
playlistURL: string;
@@ -515,7 +604,7 @@ const syncProcessedFileIDs = async () =>
export const videoPrunePermanentlyDeletedFileIDsIfNeeded = async (
deletedFileIDs: Set<number>,
) => {
if (!isDesktop) return;
if (!isHLSGenerationSupported()) return;
const existing = await savedProcessedVideoFileIDs();
if (existing.size > 0) {
@@ -544,7 +633,7 @@ export const videoPrunePermanentlyDeletedFileIDsIfNeeded = async (
* that have already been processed elsewhere.
*/
export const videoProcessingSyncIfNeeded = async () => {
if (!isDesktop) return;
if (!isHLSGenerationSupported()) return;
if (!isVideoProcessingEnabled()) return;
await syncProcessedFileIDs();
@@ -580,7 +669,7 @@ export const processVideoNewUpload = (
file: EnteFile,
processableUploadItem: ProcessableUploadItem,
) => {
if (!isDesktop) return;
if (!isHLSGenerationSupported()) return;
if (!isVideoProcessingEnabled()) return;
if (file.metadata.fileType !== FileType.video) return;
if (processableUploadItem instanceof File) {
@@ -668,7 +757,7 @@ export const isVideoProcessingEnabled = () =>
* batches, and the externally triggered processing of live uploads.
*/
const processQueue = async () => {
if (!(isDesktop && isVideoProcessingEnabled())) {
if (!isHLSGenerationSupported() || !isVideoProcessingEnabled()) {
assertionFailed(); /* we shouldn't have come here */
return;
}

View File

@@ -1,3 +1,7 @@
import {
hlsGenerationStatusSnapshot,
hlsGenerationStatusSubscribe,
} from "ente-gallery/services/video";
import { useSyncExternalStore } from "react";
import {
mlStatusSnapshot,
@@ -38,3 +42,13 @@ export const useMLStatusSnapshot = () =>
*/
export const usePeopleStateSnapshot = () =>
useSyncExternalStore(peopleStateSubscribe, peopleStateSnapshot);
/**
* A convenience hook that returns {@link hlsGenerationStatusSnapshot}, and also
* subscribes to updates.
*/
export const useHLSGenerationStatusSnapshot = () =>
useSyncExternalStore(
hlsGenerationStatusSubscribe,
hlsGenerationStatusSnapshot,
);