Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(web): Video memories on web #16500

Merged
merged 2 commits into from
Mar 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -889,6 +889,7 @@
"month": "Month",
"more": "More",
"moved_to_trash": "Moved to trash",
"mute_memories": "Mute Memories",
"my_albums": "My albums",
"name": "Name",
"name_or_nickname": "Name or nickname",
Expand Down Expand Up @@ -1302,6 +1303,7 @@
"unnamed_album": "Unnamed Album",
"unnamed_album_delete_confirmation": "Are you sure you want to delete this album?",
"unnamed_share": "Unnamed Share",
"unmute_memories": "Unmute Memories",
"unsaved_change": "Unsaved change",
"unselect_all": "Unselect all",
"unselect_all_duplicates": "Unselect all duplicates",
Expand Down
73 changes: 63 additions & 10 deletions web/src/lib/components/memory-page/memory-viewer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,14 @@
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import { type Viewport } from '$lib/stores/assets.store';
import { loadMemories, memoryStore } from '$lib/stores/memory.store';
import { locale } from '$lib/stores/preferences.store';
import { locale, videoViewerMuted } from '$lib/stores/preferences.store';
import { preferences } from '$lib/stores/user.store';
import { getAssetThumbnailUrl, handlePromiseError, memoryLaneTitle } from '$lib/utils';
import { getAssetPlaybackUrl, getAssetThumbnailUrl, handlePromiseError, memoryLaneTitle } from '$lib/utils';
import { cancelMultiselect } from '$lib/utils/asset-utils';
import { fromLocalDateTime } from '$lib/utils/timeline-util';
import {
AssetMediaSize,
AssetTypeEnum,
deleteMemory,
removeMemoryAssets,
updateMemory,
Expand All @@ -57,6 +58,8 @@
mdiPlay,
mdiPlus,
mdiSelectAll,
mdiVolumeOff,
mdiVolumeHigh,
} from '@mdi/js';
import type { NavigationTarget } from '@sveltejs/kit';
import { DateTime } from 'luxon';
Expand Down Expand Up @@ -91,9 +94,10 @@
const { isViewing } = assetViewingStore;
const viewport: Viewport = $state({ width: 0, height: 0 });
const assetInteraction = new AssetInteraction();
const progressBarController = tweened<number>(0, {
let progressBarController = tweened<number>(0, {
duration: (from: number, to: number) => (to ? 5000 * (to - from) : 0),
});
let videoPlayer: HTMLVideoElement | undefined = $state();
const memories = storeDerived(memoryStore, (memories) => {
memories = memories ?? [];
const memoryAssets: MemoryAsset[] = [];
Expand Down Expand Up @@ -139,8 +143,24 @@
return;
}

// Adjust the progress bar duration to the video length
setProgressDuration(asset);

await goto(asHref(asset));
};
const setProgressDuration = (asset: AssetResponseDto) => {
if (asset.type === AssetTypeEnum.Video) {
const timeParts = asset.duration.split(':').map(Number);
const durationInMilliseconds = (timeParts[0] * 3600 + timeParts[1] * 60 + timeParts[2]) * 1000;
progressBarController = tweened<number>(0, {
duration: (from: number, to: number) => (to ? durationInMilliseconds * (to - from) : 0),
});
} else {
progressBarController = tweened<number>(0, {
duration: (from: number, to: number) => (to ? 5000 * (to - from) : 0),
});
}
};
const handleNextAsset = () => handleNavigate(current?.next?.asset);
const handlePreviousAsset = () => handleNavigate(current?.previous?.asset);
const handleNextMemory = () => handleNavigate(current?.nextMemory?.assets[0]);
Expand All @@ -151,18 +171,21 @@
switch (action) {
case 'play': {
paused = false;
await videoPlayer?.play();
await progressBarController.set(1);
break;
}

case 'pause': {
paused = true;
videoPlayer?.pause();
await progressBarController.set($progressBarController);
break;
}

case 'reset': {
paused = false;
videoPlayer?.pause();
resetPromise = progressBarController.set(0);
break;
}
Expand Down Expand Up @@ -202,6 +225,9 @@
}

current = loadFromParams($memories, $page);

// Adjust the progress bar duration to the video length
setProgressDuration(current.asset);
};

const handleDeleteMemoryAsset = async (current?: MemoryAsset) => {
Expand Down Expand Up @@ -288,6 +314,12 @@
$effect(() => {
handlePromiseError(handleAction(galleryInView ? 'pause' : 'play'));
});

$effect(() => {
if (videoPlayer) {
videoPlayer.muted = $videoViewerMuted;
}
});
</script>

<svelte:window
Expand Down Expand Up @@ -371,6 +403,11 @@
{(current.assetIndex + 1).toLocaleString($locale)}/{current.memory.assets.length.toLocaleString($locale)}
</p>
</div>
<CircleIconButton
title={$videoViewerMuted ? $t('unmute_memories') : $t('mute_memories')}
icon={$videoViewerMuted ? mdiVolumeOff : mdiVolumeHigh}
onclick={() => ($videoViewerMuted = !$videoViewerMuted)}
/>
</div>
</ControlAppBar>

Expand Down Expand Up @@ -434,13 +471,29 @@
>
<div class="relative h-full w-full rounded-2xl bg-black">
{#key current.asset.id}
<img
transition:fade
class="h-full w-full rounded-2xl object-contain transition-all"
src={getAssetThumbnailUrl({ id: current.asset.id, size: AssetMediaSize.Preview })}
alt={current.asset.exifInfo?.description}
draggable="false"
/>
<div transition:fade class="h-full w-full">
{#if current.asset.type == AssetTypeEnum.Video}
<video
bind:this={videoPlayer}
autoplay
playsinline
class="h-full w-full rounded-2xl object-contain transition-all"
src={getAssetPlaybackUrl({ id: current.asset.id })}
poster={getAssetThumbnailUrl({ id: current.asset.id, size: AssetMediaSize.Preview })}
draggable="false"
muted={$videoViewerMuted}
transition:fade
></video>
{:else}
<img
class="h-full w-full rounded-2xl object-contain transition-all"
src={getAssetThumbnailUrl({ id: current.asset.id, size: AssetMediaSize.Preview })}
alt={current.asset.exifInfo?.description}
draggable="false"
transition:fade
/>
{/if}
</div>
{/key}

<div
Expand Down
Loading