mirror of
				https://github.com/immich-app/immich.git
				synced 2025-10-31 02:27:08 -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_motion_photo": "Play Motion Photo", | ||||
|   "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", | ||||
|   "port": "Port", | ||||
|   "preferences_settings_subtitle": "Manage the app's preferences", | ||||
|  | ||||
| @ -56,6 +56,7 @@ | ||||
|     mdiMagnifyPlusOutline, | ||||
|     mdiPresentationPlay, | ||||
|     mdiUpload, | ||||
|     mdiVideoOutline, | ||||
|   } from '@mdi/js'; | ||||
|   import type { Snippet } from 'svelte'; | ||||
|   import { t } from 'svelte-i18n'; | ||||
| @ -78,6 +79,8 @@ | ||||
|     // export let showEditorHandler: () => void; | ||||
|     onClose: () => void; | ||||
|     motionPhoto?: Snippet; | ||||
|     playOriginalVideo: boolean; | ||||
|     setPlayOriginalVideo: (value: boolean) => void; | ||||
|   } | ||||
| 
 | ||||
|   let { | ||||
| @ -97,6 +100,8 @@ | ||||
|     onShowDetail, | ||||
|     onClose, | ||||
|     motionPhoto, | ||||
|     playOriginalVideo = false, | ||||
|     setPlayOriginalVideo, | ||||
|   }: Props = $props(); | ||||
| 
 | ||||
|   const sharedLink = getSharedLink(); | ||||
| @ -245,6 +250,15 @@ | ||||
|           {#if !asset.isTrashed} | ||||
|             <SetVisibilityAction asset={toTimelineAsset(asset)} {onAction} {preAction} /> | ||||
|           {/if} | ||||
| 
 | ||||
|           {#if asset.type === AssetTypeEnum.Video} | ||||
|             <MenuOption | ||||
|               icon={mdiVideoOutline} | ||||
|               onClick={() => setPlayOriginalVideo(!playOriginalVideo)} | ||||
|               text={playOriginalVideo ? $t('play_transcoded_video') : $t('play_original_video')} | ||||
|             /> | ||||
|           {/if} | ||||
| 
 | ||||
|           <hr /> | ||||
|           <MenuOption | ||||
|             icon={mdiHeadSyncOutline} | ||||
|  | ||||
| @ -11,7 +11,7 @@ | ||||
|   import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; | ||||
|   import { closeEditorCofirm } from '$lib/stores/asset-editor.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 { user } from '$lib/stores/user.store'; | ||||
|   import { websocketEvents } from '$lib/stores/websocket'; | ||||
| @ -110,6 +110,11 @@ | ||||
|   let stack: StackResponseDto | null = $state(null); | ||||
| 
 | ||||
|   let zoomToggle = $state(() => void 0); | ||||
|   let playOriginalVideo = $state($alwaysLoadOriginalVideo); | ||||
| 
 | ||||
|   const setPlayOriginalVideo = (value: boolean) => { | ||||
|     playOriginalVideo = value; | ||||
|   }; | ||||
| 
 | ||||
|   const refreshStack = async () => { | ||||
|     if (authManager.isSharedLink) { | ||||
| @ -410,6 +415,8 @@ | ||||
|         onPlaySlideshow={() => ($slideshowState = SlideshowState.PlaySlideshow)} | ||||
|         onShowDetail={toggleDetailPanel} | ||||
|         onClose={closeViewer} | ||||
|         {playOriginalVideo} | ||||
|         {setPlayOriginalVideo} | ||||
|       > | ||||
|         {#snippet motionPhoto()} | ||||
|           <MotionPhotoAction | ||||
| @ -465,6 +472,7 @@ | ||||
|             onClose={closeViewer} | ||||
|             onVideoEnded={() => navigateAsset()} | ||||
|             onVideoStarted={handleVideoStarted} | ||||
|             {playOriginalVideo} | ||||
|           /> | ||||
|         {/if} | ||||
|       {/key} | ||||
| @ -480,6 +488,7 @@ | ||||
|               onPreviousAsset={() => navigateAsset('previous')} | ||||
|               onNextAsset={() => navigateAsset('next')} | ||||
|               onVideoEnded={() => (shouldPlayMotionPhoto = false)} | ||||
|               {playOriginalVideo} | ||||
|             /> | ||||
|           {:else if asset.exifInfo?.projectionType === ProjectionType.EQUIRECTANGULAR || (asset.originalPath && asset.originalPath | ||||
|                 .toLowerCase() | ||||
| @ -510,6 +519,7 @@ | ||||
|             onClose={closeViewer} | ||||
|             onVideoEnded={() => navigateAsset()} | ||||
|             onVideoStarted={handleVideoStarted} | ||||
|             {playOriginalVideo} | ||||
|           /> | ||||
|         {/if} | ||||
|         {#if $slideshowState === SlideshowState.None && isShared && ((album && album.isActivityEnabled) || activityManager.commentCount > 0) && !activityManager.isLoading} | ||||
|  | ||||
| @ -10,7 +10,7 @@ | ||||
|     videoViewerMuted, | ||||
|     videoViewerVolume, | ||||
|   } from '$lib/stores/preferences.store'; | ||||
|   import { getAssetPlaybackUrl, getAssetThumbnailUrl } from '$lib/utils'; | ||||
|   import { getAssetOriginalUrl, getAssetPlaybackUrl, getAssetThumbnailUrl } from '$lib/utils'; | ||||
|   import { AssetMediaSize } from '@immich/sdk'; | ||||
|   import { LoadingSpinner } from '@immich/ui'; | ||||
|   import { onDestroy, onMount } from 'svelte'; | ||||
| @ -21,6 +21,7 @@ | ||||
|     assetId: string; | ||||
|     loopVideo: boolean; | ||||
|     cacheKey: string | null; | ||||
|     playOriginalVideo: boolean; | ||||
|     onPreviousAsset?: () => void; | ||||
|     onNextAsset?: () => void; | ||||
|     onVideoEnded?: () => void; | ||||
| @ -32,6 +33,7 @@ | ||||
|     assetId, | ||||
|     loopVideo, | ||||
|     cacheKey, | ||||
|     playOriginalVideo, | ||||
|     onPreviousAsset = () => {}, | ||||
|     onNextAsset = () => {}, | ||||
|     onVideoEnded = () => {}, | ||||
| @ -48,7 +50,12 @@ | ||||
|   onMount(() => { | ||||
|     // Show video after mount to ensure fading in. | ||||
|     showVideo = true; | ||||
|     assetFileUrl = getAssetPlaybackUrl({ id: assetId, cacheKey }); | ||||
|   }); | ||||
| 
 | ||||
|   $effect(() => { | ||||
|     assetFileUrl = playOriginalVideo | ||||
|       ? getAssetOriginalUrl({ id: assetId, cacheKey }) | ||||
|       : getAssetPlaybackUrl({ id: assetId, cacheKey }); | ||||
|     if (videoPlayer) { | ||||
|       videoPlayer.load(); | ||||
|     } | ||||
|  | ||||
| @ -1,13 +1,14 @@ | ||||
| <script lang="ts"> | ||||
|   import { ProjectionType } from '$lib/constants'; | ||||
|   import VideoNativeViewer from '$lib/components/asset-viewer/video-native-viewer.svelte'; | ||||
|   import VideoPanoramaViewer from '$lib/components/asset-viewer/video-panorama-viewer.svelte'; | ||||
|   import { ProjectionType } from '$lib/constants'; | ||||
| 
 | ||||
|   interface Props { | ||||
|     assetId: string; | ||||
|     projectionType: string | null | undefined; | ||||
|     cacheKey: string | null; | ||||
|     loopVideo: boolean; | ||||
|     playOriginalVideo: boolean; | ||||
|     onClose?: () => void; | ||||
|     onPreviousAsset?: () => void; | ||||
|     onNextAsset?: () => void; | ||||
| @ -20,6 +21,7 @@ | ||||
|     projectionType, | ||||
|     cacheKey, | ||||
|     loopVideo, | ||||
|     playOriginalVideo, | ||||
|     onPreviousAsset, | ||||
|     onClose, | ||||
|     onNextAsset, | ||||
| @ -35,6 +37,7 @@ | ||||
|     {loopVideo} | ||||
|     {cacheKey} | ||||
|     {assetId} | ||||
|     {playOriginalVideo} | ||||
|     {onPreviousAsset} | ||||
|     {onNextAsset} | ||||
|     {onVideoEnded} | ||||
|  | ||||
| @ -7,6 +7,7 @@ | ||||
|   import { themeManager } from '$lib/managers/theme-manager.svelte'; | ||||
|   import { | ||||
|     alwaysLoadOriginalFile, | ||||
|     alwaysLoadOriginalVideo, | ||||
|     autoPlayVideo, | ||||
|     locale, | ||||
|     loopVideo, | ||||
| @ -119,7 +120,13 @@ | ||||
|       <div class="ms-4"> | ||||
|         <SettingSwitch title={$t('loop_videos')} subtitle={$t('loop_videos_description')} bind:checked={$loopVideo} /> | ||||
|       </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"> | ||||
|         <SettingSwitch | ||||
|           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 alwaysLoadOriginalVideo = persisted<boolean>('always-load-original-video', false, {}); | ||||
| 
 | ||||
| export const recentAlbumsDropdown = persisted<boolean>('recent-albums-open', true, {}); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user