2022-06-04 18:34:11 -05:00
|
|
|
<script lang="ts">
|
2024-05-31 13:44:04 -04:00
|
|
|
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
|
|
|
|
|
import { loopVideo as loopVideoPreference, videoViewerMuted, videoViewerVolume } from '$lib/stores/preferences.store';
|
|
|
|
|
import { getAssetPlaybackUrl, getAssetThumbnailUrl } from '$lib/utils';
|
2023-08-27 07:31:52 +03:00
|
|
|
import { handleError } from '$lib/utils/handle-error';
|
2024-05-31 13:44:04 -04:00
|
|
|
import { AssetMediaSize } from '@immich/sdk';
|
2024-11-14 08:43:25 -06:00
|
|
|
import { onDestroy, onMount } from 'svelte';
|
2024-08-29 17:40:17 +02:00
|
|
|
import { swipe } from 'svelte-gestures';
|
|
|
|
|
import type { SwipeCustomEvent } from 'svelte-gestures';
|
2024-02-14 08:09:49 -05:00
|
|
|
import { fade } from 'svelte/transition';
|
2024-06-04 21:53:00 +02:00
|
|
|
import { t } from 'svelte-i18n';
|
2025-02-26 12:55:32 -06:00
|
|
|
import { isFaceEditMode } from '$lib/stores/face-edit.svelte';
|
|
|
|
|
import FaceEditor from '$lib/components/asset-viewer/face-editor/face-editor.svelte';
|
2022-06-04 18:34:11 -05:00
|
|
|
|
2024-11-14 08:43:25 -06:00
|
|
|
interface Props {
|
|
|
|
|
assetId: string;
|
|
|
|
|
loopVideo: boolean;
|
2025-02-15 22:34:13 -05:00
|
|
|
cacheKey: string | null;
|
2024-11-14 08:43:25 -06:00
|
|
|
onPreviousAsset?: () => void;
|
|
|
|
|
onNextAsset?: () => void;
|
|
|
|
|
onVideoEnded?: () => void;
|
|
|
|
|
onVideoStarted?: () => void;
|
|
|
|
|
onClose?: () => void;
|
|
|
|
|
}
|
2022-06-04 18:34:11 -05:00
|
|
|
|
2024-11-14 08:43:25 -06:00
|
|
|
let {
|
|
|
|
|
assetId,
|
|
|
|
|
loopVideo,
|
2025-02-15 22:34:13 -05:00
|
|
|
cacheKey,
|
2024-11-14 08:43:25 -06:00
|
|
|
onPreviousAsset = () => {},
|
|
|
|
|
onNextAsset = () => {},
|
|
|
|
|
onVideoEnded = () => {},
|
|
|
|
|
onVideoStarted = () => {},
|
|
|
|
|
onClose = () => {},
|
|
|
|
|
}: Props = $props();
|
2024-05-23 20:26:22 -04:00
|
|
|
|
2024-11-14 08:43:25 -06:00
|
|
|
let videoPlayer: HTMLVideoElement | undefined = $state();
|
|
|
|
|
let isLoading = $state(true);
|
|
|
|
|
let assetFileUrl = $state('');
|
|
|
|
|
let forceMuted = $state(false);
|
2025-03-24 22:55:46 +01:00
|
|
|
let isScrubbing = $state(false);
|
2024-11-14 08:43:25 -06:00
|
|
|
|
|
|
|
|
onMount(() => {
|
|
|
|
|
if (videoPlayer) {
|
2025-02-15 22:34:13 -05:00
|
|
|
assetFileUrl = getAssetPlaybackUrl({ id: assetId, cacheKey });
|
2024-11-14 08:43:25 -06:00
|
|
|
forceMuted = false;
|
|
|
|
|
videoPlayer.load();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
onDestroy(() => {
|
|
|
|
|
if (videoPlayer) {
|
|
|
|
|
videoPlayer.src = '';
|
|
|
|
|
}
|
|
|
|
|
});
|
2024-03-21 21:01:08 +01:00
|
|
|
|
2024-06-16 17:37:25 +02:00
|
|
|
const handleCanPlay = async (video: HTMLVideoElement) => {
|
2023-08-17 11:02:12 -04:00
|
|
|
try {
|
2025-03-24 22:55:46 +01:00
|
|
|
if (!video.paused && !isScrubbing) {
|
|
|
|
|
await video.play();
|
|
|
|
|
onVideoStarted();
|
|
|
|
|
}
|
2023-08-17 11:02:12 -04:00
|
|
|
} catch (error) {
|
2024-06-16 17:37:25 +02:00
|
|
|
if (error instanceof DOMException && error.name === 'NotAllowedError' && !forceMuted) {
|
|
|
|
|
await tryForceMutedPlay(video);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2024-11-14 08:43:25 -06:00
|
|
|
|
2024-06-04 21:53:00 +02:00
|
|
|
handleError(error, $t('errors.unable_to_play_video'));
|
2023-08-17 14:52:50 -04:00
|
|
|
} finally {
|
2024-11-14 08:43:25 -06:00
|
|
|
isLoading = false;
|
2023-08-17 11:02:12 -04:00
|
|
|
}
|
2023-07-01 00:50:47 -04:00
|
|
|
};
|
2024-06-16 17:37:25 +02:00
|
|
|
|
|
|
|
|
const tryForceMutedPlay = async (video: HTMLVideoElement) => {
|
|
|
|
|
try {
|
2024-11-14 08:43:25 -06:00
|
|
|
video.muted = true;
|
2024-06-16 17:37:25 +02:00
|
|
|
await handleCanPlay(video);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
handleError(error, $t('errors.unable_to_play_video'));
|
|
|
|
|
}
|
|
|
|
|
};
|
2024-08-29 17:40:17 +02:00
|
|
|
|
|
|
|
|
const onSwipe = (event: SwipeCustomEvent) => {
|
|
|
|
|
if (event.detail.direction === 'left') {
|
|
|
|
|
onNextAsset();
|
|
|
|
|
}
|
|
|
|
|
if (event.detail.direction === 'right') {
|
|
|
|
|
onPreviousAsset();
|
|
|
|
|
}
|
|
|
|
|
};
|
2025-02-26 12:55:32 -06:00
|
|
|
|
|
|
|
|
let containerWidth = $state(0);
|
|
|
|
|
let containerHeight = $state(0);
|
|
|
|
|
|
|
|
|
|
$effect(() => {
|
|
|
|
|
if (isFaceEditMode.value) {
|
|
|
|
|
videoPlayer?.pause();
|
|
|
|
|
}
|
|
|
|
|
});
|
2022-06-04 18:34:11 -05:00
|
|
|
</script>
|
|
|
|
|
|
2025-02-26 12:55:32 -06:00
|
|
|
<div
|
|
|
|
|
transition:fade={{ duration: 150 }}
|
|
|
|
|
class="flex h-full select-none place-content-center place-items-center"
|
|
|
|
|
bind:clientWidth={containerWidth}
|
|
|
|
|
bind:clientHeight={containerHeight}
|
|
|
|
|
>
|
2023-07-01 00:50:47 -04:00
|
|
|
<video
|
2024-11-14 08:43:25 -06:00
|
|
|
bind:this={videoPlayer}
|
2024-05-14 21:31:47 +02:00
|
|
|
loop={$loopVideoPreference && loopVideo}
|
2023-08-17 14:52:50 -04:00
|
|
|
autoplay
|
|
|
|
|
playsinline
|
2023-07-01 00:50:47 -04:00
|
|
|
controls
|
|
|
|
|
class="h-full object-contain"
|
2025-01-22 22:15:38 +00:00
|
|
|
use:swipe={() => ({})}
|
2024-11-14 08:43:25 -06:00
|
|
|
onswipe={onSwipe}
|
|
|
|
|
oncanplay={(e) => handleCanPlay(e.currentTarget)}
|
|
|
|
|
onended={onVideoEnded}
|
|
|
|
|
onvolumechange={(e) => {
|
2024-06-16 17:37:25 +02:00
|
|
|
if (!forceMuted) {
|
|
|
|
|
$videoViewerMuted = e.currentTarget.muted;
|
|
|
|
|
}
|
|
|
|
|
}}
|
2025-03-24 22:55:46 +01:00
|
|
|
onseeking={() => (isScrubbing = true)}
|
|
|
|
|
onseeked={() => (isScrubbing = false)}
|
2025-03-25 08:42:23 -05:00
|
|
|
onplaying={(e) => {
|
|
|
|
|
e.currentTarget.focus();
|
|
|
|
|
}}
|
2024-11-14 08:43:25 -06:00
|
|
|
onclose={() => onClose()}
|
2024-06-16 17:37:25 +02:00
|
|
|
muted={forceMuted || $videoViewerMuted}
|
2023-07-01 00:50:47 -04:00
|
|
|
bind:volume={$videoViewerVolume}
|
2025-02-15 22:34:13 -05:00
|
|
|
poster={getAssetThumbnailUrl({ id: assetId, size: AssetMediaSize.Preview, cacheKey })}
|
2024-10-08 11:42:19 +07:00
|
|
|
src={assetFileUrl}
|
2023-07-01 00:50:47 -04:00
|
|
|
>
|
|
|
|
|
</video>
|
2023-02-15 18:56:19 +01:00
|
|
|
|
2024-11-14 08:43:25 -06:00
|
|
|
{#if isLoading}
|
2023-07-18 13:19:39 -05:00
|
|
|
<div class="absolute flex place-content-center place-items-center">
|
2023-07-01 00:50:47 -04:00
|
|
|
<LoadingSpinner />
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
2025-02-26 12:55:32 -06:00
|
|
|
|
|
|
|
|
{#if isFaceEditMode.value}
|
|
|
|
|
<FaceEditor htmlElement={videoPlayer} {containerWidth} {containerHeight} {assetId} />
|
|
|
|
|
{/if}
|
2022-06-04 18:34:11 -05:00
|
|
|
</div>
|