diff --git a/web/packages/gallery/components/viewer/data-source.ts b/web/packages/gallery/components/viewer/data-source.ts index 52fff9ff02..5e89507f84 100644 --- a/web/packages/gallery/components/viewer/data-source.ts +++ b/web/packages/gallery/components/viewer/data-source.ts @@ -253,6 +253,13 @@ export const fileViewerDidClose = () => { } }; +/** + * Options to modify the default behaviour of {@link itemDataForFile}. + */ +export interface ItemDataOpts { + videoQuality?: "auto" | "original"; +} + /** * Return the best available {@link ItemData} for rendering the given * {@link file}. @@ -264,6 +271,8 @@ export const fileViewerDidClose = () => { * At each step, we call the provided callback so that file viewer can call us * again to get the updated data. * + * @param opts Options to modify the default behaviours. + * * --- * * Detailed flow: @@ -304,15 +313,18 @@ export const fileViewerDidClose = () => { * next time the data is requested we repeat the process instead of continuing * to serve the incomplete result. */ -export const itemDataForFile = (file: EnteFile, needsRefresh: () => void) => { +export const itemDataForFile = ( + file: EnteFile, + opts: ItemDataOpts | undefined, + needsRefresh: () => void, +) => { const fileID = file.id; const fileType = file.metadata.fileType; const validTill = _state.itemDataValidTillByFileID.get(fileID); if (validTill && validTill < new Date()) { // Don't use the cached entry if it has become stale. - _state.itemDataByFileID.delete(fileID); - _state.itemDataValidTillByFileID.delete(fileID); + forgetItemDataForFileID(fileID); } let itemData = _state.itemDataByFileID.get(fileID); @@ -325,12 +337,23 @@ export const itemDataForFile = (file: EnteFile, needsRefresh: () => void) => { itemData = { fileID, fileType, isContentLoading: true }; _state.itemDataByFileID.set(file.id, itemData); _state.itemDataValidTillByFileID.delete(fileID); - void enqueueUpdates(file); + void enqueueUpdates(file, opts); } return itemData; }; +/** + * Forget item data for the given {@link file}. + * + * This is called when we change the options passed to {@link itemDataForFile}, + * and so would like to clear any previously cached data. + */ +export const forgetItemDataForFileID = (fileID: number) => { + _state.itemDataByFileID.delete(fileID); + _state.itemDataValidTillByFileID.delete(fileID); +}; + /** * Forget item data for the given {@link file} if its fetch had failed. * @@ -338,10 +361,8 @@ export const itemDataForFile = (file: EnteFile, needsRefresh: () => void) => { * full retry when they come back the next time. */ export const forgetFailedItemDataForFileID = (fileID: number) => { - if (_state.itemDataByFileID.get(fileID)?.fetchFailed) { - _state.itemDataByFileID.delete(fileID); - _state.itemDataValidTillByFileID.delete(fileID); - } + if (_state.itemDataByFileID.get(fileID)?.fetchFailed) + forgetItemDataForFileID(fileID); }; /** @@ -366,7 +387,10 @@ export const updateItemDataAlt = (updatedFile: EnteFile) => { const forgetFailedItems = () => [..._state.itemDataByFileID.keys()].forEach(forgetFailedItemDataForFileID); -const enqueueUpdates = async (file: EnteFile) => { +const enqueueUpdates = async ( + file: EnteFile, + opts: ItemDataOpts | undefined, +) => { const fileID = file.id; const fileType = file.metadata.fileType; @@ -427,7 +451,10 @@ const enqueueUpdates = async (file: EnteFile) => { try { if (isDevBuild && process.env.NEXT_PUBLIC_ENTE_WIP_VIDEO_STREAMING) { - if (file.metadata.fileType == FileType.video) { + if ( + file.metadata.fileType == FileType.video && + opts?.videoQuality != "original" + ) { const playlistData = await hlsPlaylistDataForFile(file); if (playlistData) { const { diff --git a/web/packages/gallery/components/viewer/photoswipe.ts b/web/packages/gallery/components/viewer/photoswipe.ts index e749e4fcb8..e521babbc0 100644 --- a/web/packages/gallery/components/viewer/photoswipe.ts +++ b/web/packages/gallery/components/viewer/photoswipe.ts @@ -12,9 +12,11 @@ import { fileViewerWillOpen, forgetExifForItemData, forgetFailedItemDataForFileID, + forgetItemDataForFileID, itemDataForFile, updateFileInfoExifIfNeeded, type ItemData, + type ItemDataOpts, } from "./data-source"; import { type FileViewerAnnotatedFile, @@ -297,6 +299,12 @@ export class FileViewerPhotoSwipe { const currentFileAnnotation = () => currentAnnotatedFile().annotation; + /** + * File (ID)s for which we should render the original, non-streamable, + * video even if a HLS playlist is available. + */ + const originalVideoFileIDs = new Set(); + // Provide data about slides to PhotoSwipe via callbacks // https://photoswipe.com/data-sources/#dynamically-generated-data @@ -306,7 +314,13 @@ export class FileViewerPhotoSwipe { const files = delegate.getFiles(); const file = files[index]!; - const itemData = itemDataForFile(file, () => + const opts: ItemDataOpts = { + videoQuality: originalVideoFileIDs.has(file.id) + ? "original" + : "auto", + }; + + const itemData = itemDataForFile(file, opts, () => pswp.refreshSlideContent(index), ); @@ -785,11 +799,20 @@ export class FileViewerPhotoSwipe { if (currentFileAnnotation().showDownload) handleDownload(); }; - const videoQuality = "auto"; + const onVideoQualityChange = () => { + // Currently there are only two entries in the video quality menu, + // and the callback only gets invoked if the value gets changed from + // the current value. So we can assume toggle semantics when + // implementing the logic below. - const onVideoQualityChange = (qualityMenu: MediaChromeMenu) => { - const newQuality = qualityMenu.value; - console.log(videoQuality, newQuality); + const fileID = currentAnnotatedFile().file.id; + forgetItemDataForFileID(fileID); + if (originalVideoFileIDs.has(fileID)) { + originalVideoFileIDs.delete(fileID); + } else { + originalVideoFileIDs.add(fileID); + } + this.refreshCurrentSlideContent(); }; const showIf = (element: HTMLElement, condition: boolean) =>