mirror of
				https://github.com/immich-app/immich.git
				synced 2025-10-31 10:37:11 -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