mirror of
https://github.com/immich-app/immich.git
synced 2026-02-15 21:38:54 +03:00
Compare commits
1 Commits
feat/fd-gl
...
feat/video
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a99631b12f |
60
pnpm-lock.yaml
generated
60
pnpm-lock.yaml
generated
@@ -739,7 +739,7 @@ importers:
|
||||
version: 0.4.3
|
||||
'@immich/sdk':
|
||||
specifier: file:../open-api/typescript-sdk
|
||||
version: link:../open-api/typescript-sdk
|
||||
version: file:open-api/typescript-sdk
|
||||
'@immich/ui':
|
||||
specifier: ^0.61.4
|
||||
version: 0.61.4(@sveltejs/kit@2.49.5(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.48.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.48.0)
|
||||
@@ -781,7 +781,7 @@ importers:
|
||||
version: 2.6.0
|
||||
fabric:
|
||||
specifier: ^6.5.4
|
||||
version: 6.9.1
|
||||
version: 6.9.1(encoding@0.1.13)
|
||||
geo-coordinates-parser:
|
||||
specifier: ^1.7.4
|
||||
version: 1.7.4
|
||||
@@ -809,6 +809,9 @@ importers:
|
||||
maplibre-gl:
|
||||
specifier: ^5.6.2
|
||||
version: 5.16.0
|
||||
media-chrome:
|
||||
specifier: ^4.17.2
|
||||
version: 4.17.2(react@19.2.3)
|
||||
pmtiles:
|
||||
specifier: ^4.3.0
|
||||
version: 4.3.2
|
||||
@@ -881,7 +884,7 @@ importers:
|
||||
version: 6.9.1
|
||||
'@testing-library/svelte':
|
||||
specifier: ^5.2.8
|
||||
version: 5.3.1(svelte@5.48.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.0.9)(happy-dom@20.3.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))
|
||||
version: 5.3.1(svelte@5.48.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.0.9)(happy-dom@20.3.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))
|
||||
'@testing-library/user-event':
|
||||
specifier: ^14.5.2
|
||||
version: 14.6.1(@testing-library/dom@10.4.1)
|
||||
@@ -905,7 +908,7 @@ importers:
|
||||
version: 1.5.6
|
||||
'@vitest/coverage-v8':
|
||||
specifier: ^3.0.0
|
||||
version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.0.9)(happy-dom@20.3.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))
|
||||
version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.0.9)(happy-dom@20.3.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))
|
||||
dotenv:
|
||||
specifier: ^17.0.0
|
||||
version: 17.2.3
|
||||
@@ -968,7 +971,7 @@ importers:
|
||||
version: 7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)
|
||||
vitest:
|
||||
specifier: ^3.0.0
|
||||
version: 3.2.4(@types/debug@4.1.12)(@types/node@25.0.9)(happy-dom@20.3.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)
|
||||
version: 3.2.4(@types/debug@4.1.12)(@types/node@25.0.9)(happy-dom@20.3.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)
|
||||
|
||||
packages:
|
||||
|
||||
@@ -3126,6 +3129,9 @@ packages:
|
||||
'@immich/justified-layout-wasm@0.4.3':
|
||||
resolution: {integrity: sha512-fpcQ7zPhP3Cp1bEXhONVYSUeIANa2uzaQFGKufUZQo5FO7aFT77szTVChhlCy4XaVy5R4ZvgSkA/1TJmeORz7Q==}
|
||||
|
||||
'@immich/sdk@file:open-api/typescript-sdk':
|
||||
resolution: {directory: open-api/typescript-sdk, type: directory}
|
||||
|
||||
'@immich/svelte-markdown-preprocess@0.2.1':
|
||||
resolution: {integrity: sha512-mbr/g75lO8Zh+ELCuYrZP0XB4gf2UbK8rJcGYMYxFJJzMMunV+sm9FqtV1dbwW2dpXzCZGz1XPCEZ6oo526TbA==}
|
||||
peerDependencies:
|
||||
@@ -6229,6 +6235,11 @@ packages:
|
||||
ccount@2.0.1:
|
||||
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
|
||||
|
||||
ce-la-react@0.3.2:
|
||||
resolution: {integrity: sha512-QJ6k4lOD/btI08xG8jBPxRCGXvCnusGGkTsiXk0u3NqUu/W+BXRnFD4PYjwtqh8AWmGa5LDbGk0fLQsqr0nSMA==}
|
||||
peerDependencies:
|
||||
react: '>=17.0.0'
|
||||
|
||||
chai@5.3.3:
|
||||
resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -9153,6 +9164,9 @@ packages:
|
||||
mdn-data@2.0.30:
|
||||
resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==}
|
||||
|
||||
media-chrome@4.17.2:
|
||||
resolution: {integrity: sha512-o/IgiHx0tdSVwRxxqF5H12FK31A/A8T71sv3KdAvh7b6XeBS9dXwqvIFwlR9kdEuqg3n7xpmRIuL83rmYq8FTg==}
|
||||
|
||||
media-typer@0.3.0:
|
||||
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@@ -15755,6 +15769,10 @@ snapshots:
|
||||
|
||||
'@immich/justified-layout-wasm@0.4.3': {}
|
||||
|
||||
'@immich/sdk@file:open-api/typescript-sdk':
|
||||
dependencies:
|
||||
'@oazapfts/runtime': 1.1.0
|
||||
|
||||
'@immich/svelte-markdown-preprocess@0.2.1(svelte@5.48.0)':
|
||||
dependencies:
|
||||
front-matter: 4.0.2
|
||||
@@ -17804,14 +17822,14 @@ snapshots:
|
||||
dependencies:
|
||||
svelte: 5.48.0
|
||||
|
||||
'@testing-library/svelte@5.3.1(svelte@5.48.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.0.9)(happy-dom@20.3.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))':
|
||||
'@testing-library/svelte@5.3.1(svelte@5.48.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.0.9)(happy-dom@20.3.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))':
|
||||
dependencies:
|
||||
'@testing-library/dom': 10.4.1
|
||||
'@testing-library/svelte-core': 1.0.0(svelte@5.48.0)
|
||||
svelte: 5.48.0
|
||||
optionalDependencies:
|
||||
vite: 7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)
|
||||
vitest: 3.2.4(@types/debug@4.1.12)(@types/node@25.0.9)(happy-dom@20.3.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)
|
||||
vitest: 3.2.4(@types/debug@4.1.12)(@types/node@25.0.9)(happy-dom@20.3.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)
|
||||
|
||||
'@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)':
|
||||
dependencies:
|
||||
@@ -18537,7 +18555,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.0.9)(happy-dom@20.3.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))':
|
||||
'@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.0.9)(happy-dom@20.3.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))':
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.3.0
|
||||
'@bcoe/v8-coverage': 1.0.2
|
||||
@@ -18552,7 +18570,7 @@ snapshots:
|
||||
std-env: 3.10.0
|
||||
test-exclude: 7.0.1
|
||||
tinyrainbow: 2.0.0
|
||||
vitest: 3.2.4(@types/debug@4.1.12)(@types/node@25.0.9)(happy-dom@20.3.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)
|
||||
vitest: 3.2.4(@types/debug@4.1.12)(@types/node@25.0.9)(happy-dom@20.3.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -19296,6 +19314,10 @@ snapshots:
|
||||
|
||||
ccount@2.0.1: {}
|
||||
|
||||
ce-la-react@0.3.2(react@19.2.3):
|
||||
dependencies:
|
||||
react: 19.2.3
|
||||
|
||||
chai@5.3.3:
|
||||
dependencies:
|
||||
assertion-error: 2.0.1
|
||||
@@ -20904,10 +20926,10 @@ snapshots:
|
||||
|
||||
extend@3.0.2: {}
|
||||
|
||||
fabric@6.9.1:
|
||||
fabric@6.9.1(encoding@0.1.13):
|
||||
optionalDependencies:
|
||||
canvas: 2.11.2
|
||||
jsdom: 20.0.3(canvas@2.11.2)
|
||||
canvas: 2.11.2(encoding@0.1.13)
|
||||
jsdom: 20.0.3(canvas@2.11.2(encoding@0.1.13))
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- encoding
|
||||
@@ -22059,7 +22081,7 @@ snapshots:
|
||||
dependencies:
|
||||
argparse: 2.0.1
|
||||
|
||||
jsdom@20.0.3(canvas@2.11.2):
|
||||
jsdom@20.0.3(canvas@2.11.2(encoding@0.1.13)):
|
||||
dependencies:
|
||||
abab: 2.0.6
|
||||
acorn: 8.15.0
|
||||
@@ -22088,7 +22110,7 @@ snapshots:
|
||||
ws: 8.19.0
|
||||
xml-name-validator: 4.0.0
|
||||
optionalDependencies:
|
||||
canvas: 2.11.2
|
||||
canvas: 2.11.2(encoding@0.1.13)
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- supports-color
|
||||
@@ -22781,6 +22803,12 @@ snapshots:
|
||||
|
||||
mdn-data@2.0.30: {}
|
||||
|
||||
media-chrome@4.17.2(react@19.2.3):
|
||||
dependencies:
|
||||
ce-la-react: 0.3.2(react@19.2.3)
|
||||
transitivePeerDependencies:
|
||||
- react
|
||||
|
||||
media-typer@0.3.0: {}
|
||||
|
||||
media-typer@1.1.0: {}
|
||||
@@ -26668,7 +26696,7 @@ snapshots:
|
||||
- tsx
|
||||
- yaml
|
||||
|
||||
vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.0.9)(happy-dom@20.3.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2):
|
||||
vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.0.9)(happy-dom@20.3.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2):
|
||||
dependencies:
|
||||
'@types/chai': 5.2.3
|
||||
'@vitest/expect': 3.2.4
|
||||
@@ -26697,7 +26725,7 @@ snapshots:
|
||||
'@types/debug': 4.1.12
|
||||
'@types/node': 25.0.9
|
||||
happy-dom: 20.3.0
|
||||
jsdom: 26.1.0(canvas@2.11.2)
|
||||
jsdom: 26.1.0(canvas@2.11.2(encoding@0.1.13))
|
||||
transitivePeerDependencies:
|
||||
- jiti
|
||||
- less
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
"lodash-es": "^4.17.21",
|
||||
"luxon": "^3.4.4",
|
||||
"maplibre-gl": "^5.6.2",
|
||||
"media-chrome": "^4.17.2",
|
||||
"pmtiles": "^4.3.0",
|
||||
"qrcode": "^1.5.4",
|
||||
"simple-icons": "^15.15.0",
|
||||
|
||||
@@ -513,6 +513,7 @@
|
||||
cacheKey={asset.thumbhash}
|
||||
projectionType={asset.exifInfo?.projectionType}
|
||||
loopVideo={$slideshowState !== SlideshowState.PlaySlideshow}
|
||||
showFullscreen={false}
|
||||
onPreviousAsset={() => navigateAsset('previous')}
|
||||
onNextAsset={() => navigateAsset('next')}
|
||||
onVideoEnded={() => (assetViewerManager.isPlayingMotionPhoto = false)}
|
||||
|
||||
@@ -4,24 +4,41 @@
|
||||
import { assetViewerFadeDuration } from '$lib/constants';
|
||||
import { castManager } from '$lib/managers/cast-manager.svelte';
|
||||
import { isFaceEditMode } from '$lib/stores/face-edit.svelte';
|
||||
import {
|
||||
autoPlayVideo,
|
||||
loopVideo as loopVideoPreference,
|
||||
videoViewerMuted,
|
||||
videoViewerVolume,
|
||||
} from '$lib/stores/preferences.store';
|
||||
import { autoPlayVideo, loopVideo as loopVideoPreference } from '$lib/stores/preferences.store';
|
||||
import { getAssetMediaUrl, getAssetPlaybackUrl } from '$lib/utils';
|
||||
import { AssetMediaSize } from '@immich/sdk';
|
||||
import { LoadingSpinner } from '@immich/ui';
|
||||
import { timeToSeconds } from '$lib/utils/date-time';
|
||||
import { AssetMediaSize, type AssetResponseDto } from '@immich/sdk';
|
||||
import { Icon, LoadingSpinner } from '@immich/ui';
|
||||
import {
|
||||
mdiFullscreen,
|
||||
mdiFullscreenExit,
|
||||
mdiPause,
|
||||
mdiPlay,
|
||||
mdiVolumeHigh,
|
||||
mdiVolumeLow,
|
||||
mdiVolumeMedium,
|
||||
mdiVolumeMute,
|
||||
} from '@mdi/js';
|
||||
import 'media-chrome/media-control-bar';
|
||||
import 'media-chrome/media-controller';
|
||||
import 'media-chrome/media-fullscreen-button';
|
||||
import 'media-chrome/media-mute-button';
|
||||
import 'media-chrome/media-play-button';
|
||||
import 'media-chrome/media-playback-rate-button';
|
||||
import 'media-chrome/media-time-display';
|
||||
import 'media-chrome/media-time-range';
|
||||
import 'media-chrome/media-volume-range';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { useSwipe, type SwipeCustomEvent } from 'svelte-gestures';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
interface Props {
|
||||
asset: AssetResponseDto;
|
||||
assetId: string;
|
||||
loopVideo: boolean;
|
||||
cacheKey: string | null;
|
||||
playOriginalVideo: boolean;
|
||||
showFullscreen?: boolean;
|
||||
onPreviousAsset?: () => void;
|
||||
onNextAsset?: () => void;
|
||||
onVideoEnded?: () => void;
|
||||
@@ -30,10 +47,12 @@
|
||||
}
|
||||
|
||||
let {
|
||||
asset,
|
||||
assetId,
|
||||
loopVideo,
|
||||
cacheKey,
|
||||
playOriginalVideo,
|
||||
showFullscreen = true,
|
||||
onPreviousAsset = () => {},
|
||||
onNextAsset = () => {},
|
||||
onVideoEnded = () => {},
|
||||
@@ -48,7 +67,6 @@
|
||||
? getAssetMediaUrl({ id: assetId, size: AssetMediaSize.Original, cacheKey })
|
||||
: getAssetPlaybackUrl({ id: assetId, cacheKey }),
|
||||
);
|
||||
let isScrubbing = $state(false);
|
||||
let showVideo = $state(false);
|
||||
|
||||
onMount(() => {
|
||||
@@ -71,7 +89,7 @@
|
||||
|
||||
const handleCanPlay = async (video: HTMLVideoElement) => {
|
||||
try {
|
||||
if (!video.paused && !isScrubbing) {
|
||||
if (!video.paused) {
|
||||
await video.play();
|
||||
onVideoStarted();
|
||||
}
|
||||
@@ -136,30 +154,56 @@
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<video
|
||||
bind:this={videoPlayer}
|
||||
loop={$loopVideoPreference && loopVideo}
|
||||
autoplay={$autoPlayVideo}
|
||||
playsinline
|
||||
controls
|
||||
disablePictureInPicture
|
||||
class="h-full object-contain"
|
||||
{...useSwipe(onSwipe)}
|
||||
oncanplay={(e) => handleCanPlay(e.currentTarget)}
|
||||
onended={onVideoEnded}
|
||||
onvolumechange={(e) => ($videoViewerMuted = e.currentTarget.muted)}
|
||||
onseeking={() => (isScrubbing = true)}
|
||||
onseeked={() => (isScrubbing = false)}
|
||||
onplaying={(e) => {
|
||||
e.currentTarget.focus();
|
||||
}}
|
||||
onclose={() => onClose()}
|
||||
muted={$videoViewerMuted}
|
||||
bind:volume={$videoViewerVolume}
|
||||
poster={getAssetMediaUrl({ id: assetId, size: AssetMediaSize.Preview, cacheKey })}
|
||||
src={assetFileUrl}
|
||||
>
|
||||
</video>
|
||||
<media-controller class="h-full dark" defaultduration={timeToSeconds(asset.duration)}>
|
||||
<video
|
||||
bind:this={videoPlayer}
|
||||
slot="media"
|
||||
loop={$loopVideoPreference && loopVideo}
|
||||
autoplay={$autoPlayVideo}
|
||||
disablePictureInPicture
|
||||
{...useSwipe(onSwipe)}
|
||||
class="h-full object-contain"
|
||||
oncanplay={(e) => handleCanPlay(e.currentTarget)}
|
||||
onended={onVideoEnded}
|
||||
onplaying={(e) => e.currentTarget.focus()}
|
||||
onclose={onClose}
|
||||
poster={getAssetMediaUrl({ id: asset.id, size: AssetMediaSize.Preview, cacheKey })}
|
||||
src={assetFileUrl}
|
||||
></video>
|
||||
<div part="center" slot="centered-chrome">
|
||||
<media-play-button class="rounded-full h-12 p-3 outline-none bg-light-100/60 hover:bg-light-100"
|
||||
></media-play-button>
|
||||
</div>
|
||||
<div class="flex h-16 w-full place-items-center bg-linear-to-b to-black/40 px-3">
|
||||
<media-control-bar part="bottom" class="flex justify-end gap-2 h-10 w-full">
|
||||
<media-play-button class="rounded-full p-2 outline-none">
|
||||
<Icon slot="play" icon={mdiPlay} />
|
||||
<Icon slot="pause" icon={mdiPause} />
|
||||
</media-play-button>
|
||||
<media-time-range class="rounded-lg p-2 outline-none"></media-time-range>
|
||||
<media-time-display showduration class="rounded-lg p-2 outline-none"></media-time-display>
|
||||
|
||||
<div class="media-volume-wrapper" style:position="relative">
|
||||
<media-mute-button class="rounded-full p-2 outline-none">
|
||||
<Icon slot="off" icon={mdiVolumeMute} />
|
||||
<Icon slot="low" icon={mdiVolumeLow} />
|
||||
<Icon slot="medium" icon={mdiVolumeMedium} />
|
||||
<Icon slot="high" icon={mdiVolumeHigh} />
|
||||
</media-mute-button>
|
||||
<div class="media-volume-range-wrapper">
|
||||
<media-volume-range class="rounded-lg h-10 p-2 outline-none bg-light-100"></media-volume-range>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if showFullscreen}
|
||||
<media-fullscreen-button class="rounded-full p-2 outline-none">
|
||||
<Icon slot="enter" icon={mdiFullscreen} />
|
||||
<Icon slot="exit" icon={mdiFullscreenExit} />
|
||||
</media-fullscreen-button>
|
||||
{/if}
|
||||
</media-control-bar>
|
||||
</div>
|
||||
</media-controller>
|
||||
|
||||
{#if isLoading}
|
||||
<div class="absolute flex place-content-center place-items-center">
|
||||
@@ -168,8 +212,86 @@
|
||||
{/if}
|
||||
|
||||
{#if isFaceEditMode.value}
|
||||
<FaceEditor htmlElement={videoPlayer} {containerWidth} {containerHeight} {assetId} />
|
||||
<FaceEditor htmlElement={videoPlayer!} {containerWidth} {containerHeight} {assetId} />
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
/* Always */
|
||||
media-controller {
|
||||
--media-control-background: none;
|
||||
--media-control-hover-background: var(--immich-ui-light-100);
|
||||
--media-focus-box-shadow: 0 0 0 2px var(--immich-ui-dark);
|
||||
--media-font: none;
|
||||
--media-primary-color: var(--immich-ui-dark);
|
||||
--media-time-range-buffered-color: var(--immich-ui-dark-400);
|
||||
--media-range-track-border-radius: 2px;
|
||||
--media-range-padding: calc(var(--spacing) * 1);
|
||||
--media-tooltip-arrow-display: none;
|
||||
--media-tooltip-border-radius: var(--radius-lg);
|
||||
--media-tooltip-background-color: var(--immich-ui-light-200);
|
||||
--media-tooltip-distance: 8px;
|
||||
--media-tooltip-padding: calc(var(--spacing) * 4) calc(var(--spacing) * 3.5);
|
||||
}
|
||||
|
||||
/* Needs special handling for some reason */
|
||||
media-time-display:focus-visible {
|
||||
box-shadow: var(--media-focus-box-shadow);
|
||||
}
|
||||
|
||||
/* Small screens */
|
||||
media-controller {
|
||||
--bottom-play-button-display: none;
|
||||
--center-play-button-display: inline-flex;
|
||||
--media-time-range-display: none;
|
||||
}
|
||||
|
||||
/* Larger screens */
|
||||
*[breakpointsm] {
|
||||
--bottom-play-button-display: flex;
|
||||
--center-play-button-display: none;
|
||||
--media-time-range-display: flex;
|
||||
}
|
||||
|
||||
*::part(bottom) {
|
||||
--media-play-button-display: var(--bottom-play-button-display);
|
||||
}
|
||||
|
||||
*::part(center) {
|
||||
--media-play-button-display: var(--center-play-button-display);
|
||||
}
|
||||
|
||||
*::part(tooltip) {
|
||||
font-size: var(--text-xs);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* should be media-volume-range[mediavolumeunavailable] */
|
||||
*[mediavolumeunavailable] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.media-volume-wrapper {
|
||||
--media-tooltip-display: none;
|
||||
}
|
||||
|
||||
.media-volume-range-wrapper {
|
||||
transform: rotate(-90deg);
|
||||
position: absolute;
|
||||
top: -70px;
|
||||
left: -30px;
|
||||
opacity: 0;
|
||||
--media-control-background: var(--media-control-hover-background);
|
||||
}
|
||||
|
||||
media-mute-button:hover + .media-volume-range-wrapper,
|
||||
media-mute-button:focus + .media-volume-range-wrapper,
|
||||
media-mute-button:focus-within + .media-volume-range-wrapper,
|
||||
.media-volume-range-wrapper:hover,
|
||||
.media-volume-range-wrapper:focus,
|
||||
.media-volume-range-wrapper:focus-within {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
cacheKey: string | null;
|
||||
loopVideo: boolean;
|
||||
playOriginalVideo: boolean;
|
||||
showFullscreen?: boolean;
|
||||
onClose?: () => void;
|
||||
onPreviousAsset?: () => void;
|
||||
onNextAsset?: () => void;
|
||||
@@ -25,6 +26,7 @@
|
||||
cacheKey,
|
||||
loopVideo,
|
||||
playOriginalVideo,
|
||||
showFullscreen = true,
|
||||
onPreviousAsset,
|
||||
onClose,
|
||||
onNextAsset,
|
||||
@@ -41,8 +43,10 @@
|
||||
<VideoNativeViewer
|
||||
{loopVideo}
|
||||
{cacheKey}
|
||||
{asset}
|
||||
assetId={effectiveAssetId}
|
||||
{playOriginalVideo}
|
||||
{showFullscreen}
|
||||
{onPreviousAsset}
|
||||
{onNextAsset}
|
||||
{onVideoEnded}
|
||||
|
||||
@@ -10,11 +10,9 @@
|
||||
interface Props {
|
||||
asset: TimelineAsset;
|
||||
videoPlayer: HTMLVideoElement | undefined;
|
||||
videoViewerMuted?: boolean;
|
||||
videoViewerVolume?: number;
|
||||
}
|
||||
|
||||
let { asset, videoPlayer = $bindable(), videoViewerVolume, videoViewerMuted }: Props = $props();
|
||||
let { asset, videoPlayer = $bindable() }: Props = $props();
|
||||
|
||||
let showVideo: boolean = $state(false);
|
||||
|
||||
@@ -34,8 +32,6 @@
|
||||
src={getAssetPlaybackUrl({ id: asset.id })}
|
||||
poster={getAssetMediaUrl({ id: asset.id, size: AssetMediaSize.Preview })}
|
||||
draggable="false"
|
||||
muted={videoViewerMuted}
|
||||
volume={videoViewerVolume}
|
||||
></video>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||
import { memoryStore, type MemoryAsset } from '$lib/stores/memory.store.svelte';
|
||||
import { locale, videoViewerMuted, videoViewerVolume } from '$lib/stores/preferences.store';
|
||||
import { locale } from '$lib/stores/preferences.store';
|
||||
import { preferences } from '$lib/stores/user.store';
|
||||
import { getAssetMediaUrl, handlePromiseError, memoryLaneTitle } from '$lib/utils';
|
||||
import { cancelMultiselect } from '$lib/utils/asset-utils';
|
||||
@@ -303,7 +303,6 @@
|
||||
|
||||
$effect(() => {
|
||||
if (videoPlayer) {
|
||||
videoPlayer.muted = $videoViewerMuted;
|
||||
initPlayer();
|
||||
}
|
||||
});
|
||||
@@ -407,9 +406,9 @@
|
||||
shape="round"
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
aria-label={$videoViewerMuted ? $t('unmute_memories') : $t('mute_memories')}
|
||||
icon={$videoViewerMuted ? mdiVolumeOff : mdiVolumeHigh}
|
||||
onclick={() => ($videoViewerMuted = !$videoViewerMuted)}
|
||||
aria-label={videoPlayer?.muted ? $t('unmute_memories') : $t('mute_memories')}
|
||||
icon={videoPlayer?.muted ? mdiVolumeOff : mdiVolumeHigh}
|
||||
onclick={() => (videoPlayer ? (videoPlayer.muted = !videoPlayer.muted) : {})}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -483,12 +482,7 @@
|
||||
<div class="relative h-full w-full rounded-2xl bg-black">
|
||||
{#key current.asset.id}
|
||||
{#if current.asset.isVideo}
|
||||
<MemoryVideoViewer
|
||||
asset={current.asset}
|
||||
bind:videoPlayer
|
||||
videoViewerMuted={$videoViewerMuted}
|
||||
videoViewerVolume={$videoViewerVolume}
|
||||
/>
|
||||
<MemoryVideoViewer asset={current.asset} bind:videoPlayer />
|
||||
{:else}
|
||||
<MemoryPhotoViewer asset={current.asset} onImageLoad={resetAndPlay} />
|
||||
{/if}
|
||||
|
||||
@@ -56,9 +56,6 @@ const persistedObject = <T>(key: string, defaults: T) =>
|
||||
|
||||
export const mapSettings = persistedObject<MapSettings>('map-settings', defaultMapSettings);
|
||||
|
||||
export const videoViewerVolume = persisted<number>('video-viewer-volume', 1, {});
|
||||
export const videoViewerMuted = persisted<boolean>('video-viewer-muted', false, {});
|
||||
|
||||
export interface AlbumViewSettings {
|
||||
view: string;
|
||||
filter: string;
|
||||
|
||||
Reference in New Issue
Block a user