mirror of
https://github.com/immich-app/immich.git
synced 2025-10-31 18:47:09 -04:00
feat(web): load original videos (#20041)
* added user preference for always loading original video added ability to toggle between transcoded/original in the video viewer add fix to static check error * address PR comments * Update asset-viewer-nav-bar.svelte Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com> --------- Co-authored-by: Mert <101130780+mertalev@users.noreply.github.com> Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>
This commit is contained in:
parent
c73e3dacea
commit
f721a62776
@ -1540,6 +1540,9 @@
|
|||||||
"play_memories": "Play memories",
|
"play_memories": "Play memories",
|
||||||
"play_motion_photo": "Play Motion Photo",
|
"play_motion_photo": "Play Motion Photo",
|
||||||
"play_or_pause_video": "Play or pause video",
|
"play_or_pause_video": "Play or pause video",
|
||||||
|
"play_original_video": "Play original video",
|
||||||
|
"play_original_video_setting_description": "Prefer playback of original videos rather than transcoded videos. If original asset is not compatible it may not playback correctly.",
|
||||||
|
"play_transcoded_video": "Play transcoded video",
|
||||||
"please_auth_to_access": "Please authenticate to access",
|
"please_auth_to_access": "Please authenticate to access",
|
||||||
"port": "Port",
|
"port": "Port",
|
||||||
"preferences_settings_subtitle": "Manage the app's preferences",
|
"preferences_settings_subtitle": "Manage the app's preferences",
|
||||||
|
|||||||
@ -56,6 +56,7 @@
|
|||||||
mdiMagnifyPlusOutline,
|
mdiMagnifyPlusOutline,
|
||||||
mdiPresentationPlay,
|
mdiPresentationPlay,
|
||||||
mdiUpload,
|
mdiUpload,
|
||||||
|
mdiVideoOutline,
|
||||||
} from '@mdi/js';
|
} from '@mdi/js';
|
||||||
import type { Snippet } from 'svelte';
|
import type { Snippet } from 'svelte';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
@ -78,6 +79,8 @@
|
|||||||
// export let showEditorHandler: () => void;
|
// export let showEditorHandler: () => void;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
motionPhoto?: Snippet;
|
motionPhoto?: Snippet;
|
||||||
|
playOriginalVideo: boolean;
|
||||||
|
setPlayOriginalVideo: (value: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
let {
|
||||||
@ -97,6 +100,8 @@
|
|||||||
onShowDetail,
|
onShowDetail,
|
||||||
onClose,
|
onClose,
|
||||||
motionPhoto,
|
motionPhoto,
|
||||||
|
playOriginalVideo = false,
|
||||||
|
setPlayOriginalVideo,
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
const sharedLink = getSharedLink();
|
const sharedLink = getSharedLink();
|
||||||
@ -245,6 +250,15 @@
|
|||||||
{#if !asset.isTrashed}
|
{#if !asset.isTrashed}
|
||||||
<SetVisibilityAction asset={toTimelineAsset(asset)} {onAction} {preAction} />
|
<SetVisibilityAction asset={toTimelineAsset(asset)} {onAction} {preAction} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if asset.type === AssetTypeEnum.Video}
|
||||||
|
<MenuOption
|
||||||
|
icon={mdiVideoOutline}
|
||||||
|
onClick={() => setPlayOriginalVideo(!playOriginalVideo)}
|
||||||
|
text={playOriginalVideo ? $t('play_transcoded_video') : $t('play_original_video')}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
<MenuOption
|
<MenuOption
|
||||||
icon={mdiHeadSyncOutline}
|
icon={mdiHeadSyncOutline}
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
||||||
import { closeEditorCofirm } from '$lib/stores/asset-editor.store';
|
import { closeEditorCofirm } from '$lib/stores/asset-editor.store';
|
||||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||||
import { isShowDetail } from '$lib/stores/preferences.store';
|
import { alwaysLoadOriginalVideo, isShowDetail } from '$lib/stores/preferences.store';
|
||||||
import { SlideshowNavigation, SlideshowState, slideshowStore } from '$lib/stores/slideshow.store';
|
import { SlideshowNavigation, SlideshowState, slideshowStore } from '$lib/stores/slideshow.store';
|
||||||
import { user } from '$lib/stores/user.store';
|
import { user } from '$lib/stores/user.store';
|
||||||
import { websocketEvents } from '$lib/stores/websocket';
|
import { websocketEvents } from '$lib/stores/websocket';
|
||||||
@ -110,6 +110,11 @@
|
|||||||
let stack: StackResponseDto | null = $state(null);
|
let stack: StackResponseDto | null = $state(null);
|
||||||
|
|
||||||
let zoomToggle = $state(() => void 0);
|
let zoomToggle = $state(() => void 0);
|
||||||
|
let playOriginalVideo = $state($alwaysLoadOriginalVideo);
|
||||||
|
|
||||||
|
const setPlayOriginalVideo = (value: boolean) => {
|
||||||
|
playOriginalVideo = value;
|
||||||
|
};
|
||||||
|
|
||||||
const refreshStack = async () => {
|
const refreshStack = async () => {
|
||||||
if (authManager.isSharedLink) {
|
if (authManager.isSharedLink) {
|
||||||
@ -410,6 +415,8 @@
|
|||||||
onPlaySlideshow={() => ($slideshowState = SlideshowState.PlaySlideshow)}
|
onPlaySlideshow={() => ($slideshowState = SlideshowState.PlaySlideshow)}
|
||||||
onShowDetail={toggleDetailPanel}
|
onShowDetail={toggleDetailPanel}
|
||||||
onClose={closeViewer}
|
onClose={closeViewer}
|
||||||
|
{playOriginalVideo}
|
||||||
|
{setPlayOriginalVideo}
|
||||||
>
|
>
|
||||||
{#snippet motionPhoto()}
|
{#snippet motionPhoto()}
|
||||||
<MotionPhotoAction
|
<MotionPhotoAction
|
||||||
@ -465,6 +472,7 @@
|
|||||||
onClose={closeViewer}
|
onClose={closeViewer}
|
||||||
onVideoEnded={() => navigateAsset()}
|
onVideoEnded={() => navigateAsset()}
|
||||||
onVideoStarted={handleVideoStarted}
|
onVideoStarted={handleVideoStarted}
|
||||||
|
{playOriginalVideo}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{/key}
|
{/key}
|
||||||
@ -480,6 +488,7 @@
|
|||||||
onPreviousAsset={() => navigateAsset('previous')}
|
onPreviousAsset={() => navigateAsset('previous')}
|
||||||
onNextAsset={() => navigateAsset('next')}
|
onNextAsset={() => navigateAsset('next')}
|
||||||
onVideoEnded={() => (shouldPlayMotionPhoto = false)}
|
onVideoEnded={() => (shouldPlayMotionPhoto = false)}
|
||||||
|
{playOriginalVideo}
|
||||||
/>
|
/>
|
||||||
{:else if asset.exifInfo?.projectionType === ProjectionType.EQUIRECTANGULAR || (asset.originalPath && asset.originalPath
|
{:else if asset.exifInfo?.projectionType === ProjectionType.EQUIRECTANGULAR || (asset.originalPath && asset.originalPath
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
@ -510,6 +519,7 @@
|
|||||||
onClose={closeViewer}
|
onClose={closeViewer}
|
||||||
onVideoEnded={() => navigateAsset()}
|
onVideoEnded={() => navigateAsset()}
|
||||||
onVideoStarted={handleVideoStarted}
|
onVideoStarted={handleVideoStarted}
|
||||||
|
{playOriginalVideo}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{#if $slideshowState === SlideshowState.None && isShared && ((album && album.isActivityEnabled) || activityManager.commentCount > 0) && !activityManager.isLoading}
|
{#if $slideshowState === SlideshowState.None && isShared && ((album && album.isActivityEnabled) || activityManager.commentCount > 0) && !activityManager.isLoading}
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
videoViewerMuted,
|
videoViewerMuted,
|
||||||
videoViewerVolume,
|
videoViewerVolume,
|
||||||
} from '$lib/stores/preferences.store';
|
} from '$lib/stores/preferences.store';
|
||||||
import { getAssetPlaybackUrl, getAssetThumbnailUrl } from '$lib/utils';
|
import { getAssetOriginalUrl, getAssetPlaybackUrl, getAssetThumbnailUrl } from '$lib/utils';
|
||||||
import { AssetMediaSize } from '@immich/sdk';
|
import { AssetMediaSize } from '@immich/sdk';
|
||||||
import { LoadingSpinner } from '@immich/ui';
|
import { LoadingSpinner } from '@immich/ui';
|
||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
@ -21,6 +21,7 @@
|
|||||||
assetId: string;
|
assetId: string;
|
||||||
loopVideo: boolean;
|
loopVideo: boolean;
|
||||||
cacheKey: string | null;
|
cacheKey: string | null;
|
||||||
|
playOriginalVideo: boolean;
|
||||||
onPreviousAsset?: () => void;
|
onPreviousAsset?: () => void;
|
||||||
onNextAsset?: () => void;
|
onNextAsset?: () => void;
|
||||||
onVideoEnded?: () => void;
|
onVideoEnded?: () => void;
|
||||||
@ -32,6 +33,7 @@
|
|||||||
assetId,
|
assetId,
|
||||||
loopVideo,
|
loopVideo,
|
||||||
cacheKey,
|
cacheKey,
|
||||||
|
playOriginalVideo,
|
||||||
onPreviousAsset = () => {},
|
onPreviousAsset = () => {},
|
||||||
onNextAsset = () => {},
|
onNextAsset = () => {},
|
||||||
onVideoEnded = () => {},
|
onVideoEnded = () => {},
|
||||||
@ -48,7 +50,12 @@
|
|||||||
onMount(() => {
|
onMount(() => {
|
||||||
// Show video after mount to ensure fading in.
|
// Show video after mount to ensure fading in.
|
||||||
showVideo = true;
|
showVideo = true;
|
||||||
assetFileUrl = getAssetPlaybackUrl({ id: assetId, cacheKey });
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
assetFileUrl = playOriginalVideo
|
||||||
|
? getAssetOriginalUrl({ id: assetId, cacheKey })
|
||||||
|
: getAssetPlaybackUrl({ id: assetId, cacheKey });
|
||||||
if (videoPlayer) {
|
if (videoPlayer) {
|
||||||
videoPlayer.load();
|
videoPlayer.load();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ProjectionType } from '$lib/constants';
|
|
||||||
import VideoNativeViewer from '$lib/components/asset-viewer/video-native-viewer.svelte';
|
import VideoNativeViewer from '$lib/components/asset-viewer/video-native-viewer.svelte';
|
||||||
import VideoPanoramaViewer from '$lib/components/asset-viewer/video-panorama-viewer.svelte';
|
import VideoPanoramaViewer from '$lib/components/asset-viewer/video-panorama-viewer.svelte';
|
||||||
|
import { ProjectionType } from '$lib/constants';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
assetId: string;
|
assetId: string;
|
||||||
projectionType: string | null | undefined;
|
projectionType: string | null | undefined;
|
||||||
cacheKey: string | null;
|
cacheKey: string | null;
|
||||||
loopVideo: boolean;
|
loopVideo: boolean;
|
||||||
|
playOriginalVideo: boolean;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
onPreviousAsset?: () => void;
|
onPreviousAsset?: () => void;
|
||||||
onNextAsset?: () => void;
|
onNextAsset?: () => void;
|
||||||
@ -20,6 +21,7 @@
|
|||||||
projectionType,
|
projectionType,
|
||||||
cacheKey,
|
cacheKey,
|
||||||
loopVideo,
|
loopVideo,
|
||||||
|
playOriginalVideo,
|
||||||
onPreviousAsset,
|
onPreviousAsset,
|
||||||
onClose,
|
onClose,
|
||||||
onNextAsset,
|
onNextAsset,
|
||||||
@ -35,6 +37,7 @@
|
|||||||
{loopVideo}
|
{loopVideo}
|
||||||
{cacheKey}
|
{cacheKey}
|
||||||
{assetId}
|
{assetId}
|
||||||
|
{playOriginalVideo}
|
||||||
{onPreviousAsset}
|
{onPreviousAsset}
|
||||||
{onNextAsset}
|
{onNextAsset}
|
||||||
{onVideoEnded}
|
{onVideoEnded}
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
import { themeManager } from '$lib/managers/theme-manager.svelte';
|
import { themeManager } from '$lib/managers/theme-manager.svelte';
|
||||||
import {
|
import {
|
||||||
alwaysLoadOriginalFile,
|
alwaysLoadOriginalFile,
|
||||||
|
alwaysLoadOriginalVideo,
|
||||||
autoPlayVideo,
|
autoPlayVideo,
|
||||||
locale,
|
locale,
|
||||||
loopVideo,
|
loopVideo,
|
||||||
@ -119,7 +120,13 @@
|
|||||||
<div class="ms-4">
|
<div class="ms-4">
|
||||||
<SettingSwitch title={$t('loop_videos')} subtitle={$t('loop_videos_description')} bind:checked={$loopVideo} />
|
<SettingSwitch title={$t('loop_videos')} subtitle={$t('loop_videos_description')} bind:checked={$loopVideo} />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="ms-4">
|
||||||
|
<SettingSwitch
|
||||||
|
title={$t('play_original_video')}
|
||||||
|
subtitle={$t('play_original_video_setting_description')}
|
||||||
|
bind:checked={$alwaysLoadOriginalVideo}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div class="ms-4">
|
<div class="ms-4">
|
||||||
<SettingSwitch
|
<SettingSwitch
|
||||||
title={$t('permanent_deletion_warning')}
|
title={$t('permanent_deletion_warning')}
|
||||||
|
|||||||
@ -148,4 +148,6 @@ export const loopVideo = persisted<boolean>('loop-video', true, {});
|
|||||||
|
|
||||||
export const autoPlayVideo = persisted<boolean>('auto-play-video', true, {});
|
export const autoPlayVideo = persisted<boolean>('auto-play-video', true, {});
|
||||||
|
|
||||||
|
export const alwaysLoadOriginalVideo = persisted<boolean>('always-load-original-video', false, {});
|
||||||
|
|
||||||
export const recentAlbumsDropdown = persisted<boolean>('recent-albums-open', true, {});
|
export const recentAlbumsDropdown = persisted<boolean>('recent-albums-open', true, {});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user