mirror of
https://github.com/immich-app/immich.git
synced 2025-05-31 20:25:32 -04:00
feat(web): Video memories on web (#16500)
* Video memories on web * switched mixed up strings
This commit is contained in:
parent
8b24c31d20
commit
7bbc1d9f68
@ -890,6 +890,7 @@
|
|||||||
"month": "Month",
|
"month": "Month",
|
||||||
"more": "More",
|
"more": "More",
|
||||||
"moved_to_trash": "Moved to trash",
|
"moved_to_trash": "Moved to trash",
|
||||||
|
"mute_memories": "Mute Memories",
|
||||||
"my_albums": "My albums",
|
"my_albums": "My albums",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"name_or_nickname": "Name or nickname",
|
"name_or_nickname": "Name or nickname",
|
||||||
@ -1304,6 +1305,7 @@
|
|||||||
"unnamed_album": "Unnamed Album",
|
"unnamed_album": "Unnamed Album",
|
||||||
"unnamed_album_delete_confirmation": "Are you sure you want to delete this album?",
|
"unnamed_album_delete_confirmation": "Are you sure you want to delete this album?",
|
||||||
"unnamed_share": "Unnamed Share",
|
"unnamed_share": "Unnamed Share",
|
||||||
|
"unmute_memories": "Unmute Memories",
|
||||||
"unsaved_change": "Unsaved change",
|
"unsaved_change": "Unsaved change",
|
||||||
"unselect_all": "Unselect all",
|
"unselect_all": "Unselect all",
|
||||||
"unselect_all_duplicates": "Unselect all duplicates",
|
"unselect_all_duplicates": "Unselect all duplicates",
|
||||||
|
@ -28,13 +28,14 @@
|
|||||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||||
import { type Viewport } from '$lib/stores/assets.store';
|
import { type Viewport } from '$lib/stores/assets.store';
|
||||||
import { loadMemories, memoryStore } from '$lib/stores/memory.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 { 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 { cancelMultiselect } from '$lib/utils/asset-utils';
|
||||||
import { fromLocalDateTime } from '$lib/utils/timeline-util';
|
import { fromLocalDateTime } from '$lib/utils/timeline-util';
|
||||||
import {
|
import {
|
||||||
AssetMediaSize,
|
AssetMediaSize,
|
||||||
|
AssetTypeEnum,
|
||||||
deleteMemory,
|
deleteMemory,
|
||||||
removeMemoryAssets,
|
removeMemoryAssets,
|
||||||
updateMemory,
|
updateMemory,
|
||||||
@ -57,6 +58,8 @@
|
|||||||
mdiPlay,
|
mdiPlay,
|
||||||
mdiPlus,
|
mdiPlus,
|
||||||
mdiSelectAll,
|
mdiSelectAll,
|
||||||
|
mdiVolumeOff,
|
||||||
|
mdiVolumeHigh,
|
||||||
} from '@mdi/js';
|
} from '@mdi/js';
|
||||||
import type { NavigationTarget } from '@sveltejs/kit';
|
import type { NavigationTarget } from '@sveltejs/kit';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
@ -91,9 +94,10 @@
|
|||||||
const { isViewing } = assetViewingStore;
|
const { isViewing } = assetViewingStore;
|
||||||
const viewport: Viewport = $state({ width: 0, height: 0 });
|
const viewport: Viewport = $state({ width: 0, height: 0 });
|
||||||
const assetInteraction = new AssetInteraction();
|
const assetInteraction = new AssetInteraction();
|
||||||
const progressBarController = tweened<number>(0, {
|
let progressBarController = tweened<number>(0, {
|
||||||
duration: (from: number, to: number) => (to ? 5000 * (to - from) : 0),
|
duration: (from: number, to: number) => (to ? 5000 * (to - from) : 0),
|
||||||
});
|
});
|
||||||
|
let videoPlayer: HTMLVideoElement | undefined = $state();
|
||||||
const memories = storeDerived(memoryStore, (memories) => {
|
const memories = storeDerived(memoryStore, (memories) => {
|
||||||
memories = memories ?? [];
|
memories = memories ?? [];
|
||||||
const memoryAssets: MemoryAsset[] = [];
|
const memoryAssets: MemoryAsset[] = [];
|
||||||
@ -139,8 +143,24 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adjust the progress bar duration to the video length
|
||||||
|
setProgressDuration(asset);
|
||||||
|
|
||||||
await goto(asHref(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 handleNextAsset = () => handleNavigate(current?.next?.asset);
|
||||||
const handlePreviousAsset = () => handleNavigate(current?.previous?.asset);
|
const handlePreviousAsset = () => handleNavigate(current?.previous?.asset);
|
||||||
const handleNextMemory = () => handleNavigate(current?.nextMemory?.assets[0]);
|
const handleNextMemory = () => handleNavigate(current?.nextMemory?.assets[0]);
|
||||||
@ -151,18 +171,21 @@
|
|||||||
switch (action) {
|
switch (action) {
|
||||||
case 'play': {
|
case 'play': {
|
||||||
paused = false;
|
paused = false;
|
||||||
|
await videoPlayer?.play();
|
||||||
await progressBarController.set(1);
|
await progressBarController.set(1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'pause': {
|
case 'pause': {
|
||||||
paused = true;
|
paused = true;
|
||||||
|
videoPlayer?.pause();
|
||||||
await progressBarController.set($progressBarController);
|
await progressBarController.set($progressBarController);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'reset': {
|
case 'reset': {
|
||||||
paused = false;
|
paused = false;
|
||||||
|
videoPlayer?.pause();
|
||||||
resetPromise = progressBarController.set(0);
|
resetPromise = progressBarController.set(0);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -203,6 +226,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
current = loadFromParams($memories, $page);
|
current = loadFromParams($memories, $page);
|
||||||
|
|
||||||
|
// Adjust the progress bar duration to the video length
|
||||||
|
setProgressDuration(current.asset);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteMemoryAsset = async (current?: MemoryAsset) => {
|
const handleDeleteMemoryAsset = async (current?: MemoryAsset) => {
|
||||||
@ -290,6 +316,12 @@
|
|||||||
$effect(() => {
|
$effect(() => {
|
||||||
handlePromiseError(handleAction(galleryInView ? 'pause' : 'play'));
|
handlePromiseError(handleAction(galleryInView ? 'pause' : 'play'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (videoPlayer) {
|
||||||
|
videoPlayer.muted = $videoViewerMuted;
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window
|
<svelte:window
|
||||||
@ -373,6 +405,11 @@
|
|||||||
{(current.assetIndex + 1).toLocaleString($locale)}/{current.memory.assets.length.toLocaleString($locale)}
|
{(current.assetIndex + 1).toLocaleString($locale)}/{current.memory.assets.length.toLocaleString($locale)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<CircleIconButton
|
||||||
|
title={$videoViewerMuted ? $t('unmute_memories') : $t('mute_memories')}
|
||||||
|
icon={$videoViewerMuted ? mdiVolumeOff : mdiVolumeHigh}
|
||||||
|
onclick={() => ($videoViewerMuted = !$videoViewerMuted)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</ControlAppBar>
|
</ControlAppBar>
|
||||||
|
|
||||||
@ -436,13 +473,29 @@
|
|||||||
>
|
>
|
||||||
<div class="relative h-full w-full rounded-2xl bg-black">
|
<div class="relative h-full w-full rounded-2xl bg-black">
|
||||||
{#key current.asset.id}
|
{#key current.asset.id}
|
||||||
<img
|
<div transition:fade class="h-full w-full">
|
||||||
transition:fade
|
{#if current.asset.type == AssetTypeEnum.Video}
|
||||||
class="h-full w-full rounded-2xl object-contain transition-all"
|
<video
|
||||||
src={getAssetThumbnailUrl({ id: current.asset.id, size: AssetMediaSize.Preview })}
|
bind:this={videoPlayer}
|
||||||
alt={current.asset.exifInfo?.description}
|
autoplay
|
||||||
draggable="false"
|
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}
|
{/key}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
Loading…
x
Reference in New Issue
Block a user