-              
+      {#each Object.keys($downloadAssets) as downloadKey (downloadKey)}
+        {@const download = $downloadAssets[downloadKey]}
+        
+          
+            
+              
■ {downloadKey}
+              {#if download.total}
+                
{asByteUnitString(download.total, $locale)}
+              {/if}
             
+            
+              
+              
+                {download.percentage}%
+              
+            
+          
+          
+             abort(downloadKey, download)} size="20" logo={Close} forceDark />
           
         
       {/each}
diff --git a/web/src/lib/components/photos-page/actions/download-action.svelte b/web/src/lib/components/photos-page/actions/download-action.svelte
index 43b3a9ae..e1fb24c0 100644
--- a/web/src/lib/components/photos-page/actions/download-action.svelte
+++ b/web/src/lib/components/photos-page/actions/download-action.svelte
@@ -14,12 +14,13 @@
   const handleDownloadFiles = async () => {
     const assets = Array.from(getAssets());
     if (assets.length === 1) {
-      await downloadFile(assets[0], sharedLinkKey);
       clearSelect();
+      await downloadFile(assets[0], sharedLinkKey);
       return;
     }
 
-    await downloadArchive(filename, { assetIds: assets.map((asset) => asset.id) }, clearSelect, sharedLinkKey);
+    clearSelect();
+    await downloadArchive(filename, { assetIds: assets.map((asset) => asset.id) }, sharedLinkKey);
   };
 
 
diff --git a/web/src/lib/components/share-page/individual-shared-viewer.svelte b/web/src/lib/components/share-page/individual-shared-viewer.svelte
index aa16f1b4..0f883a4a 100644
--- a/web/src/lib/components/share-page/individual-shared-viewer.svelte
+++ b/web/src/lib/components/share-page/individual-shared-viewer.svelte
@@ -35,12 +35,7 @@
   });
 
   const downloadAssets = async () => {
-    await downloadArchive(
-      `immich-shared.zip`,
-      { assetIds: assets.map((asset) => asset.id) },
-      undefined,
-      sharedLink.key,
-    );
+    await downloadArchive(`immich-shared.zip`, { assetIds: assets.map((asset) => asset.id) }, sharedLink.key);
   };
 
   const handleUploadAssets = async (files: File[] = []) => {
diff --git a/web/src/lib/stores/download.ts b/web/src/lib/stores/download.ts
index a7a9f81c..7dd13b18 100644
--- a/web/src/lib/stores/download.ts
+++ b/web/src/lib/stores/download.ts
@@ -1,6 +1,13 @@
 import { derived, writable } from 'svelte/store';
 
-export const downloadAssets = writable
>({});
+export interface DownloadProgress {
+  progress: number;
+  total: number;
+  percentage: number;
+  abort: AbortController | null;
+}
+
+export const downloadAssets = writable>({});
 
 export const isDownloading = derived(downloadAssets, ($downloadAssets) => {
   if (Object.keys($downloadAssets).length == 0) {
@@ -10,17 +17,35 @@ export const isDownloading = derived(downloadAssets, ($downloadAssets) => {
   return true;
 });
 
-const update = (key: string, value: number | null) => {
+const update = (key: string, value: Partial | null) => {
   downloadAssets.update((state) => {
     const newState = { ...state };
+
     if (value === null) {
       delete newState[key];
-    } else {
-      newState[key] = value;
+      return newState;
     }
+
+    if (!newState[key]) {
+      newState[key] = { progress: 0, total: 0, percentage: 0, abort: null };
+    }
+
+    const item = newState[key];
+    Object.assign(item, value);
+    item.percentage = Math.min(Math.floor((item.progress / item.total) * 100), 100);
+
     return newState;
   });
 };
 
-export const clearDownload = (key: string) => update(key, null);
-export const updateDownload = (key: string, value: number) => update(key, value);
+export const downloadManager = {
+  add: (key: string, total: number, abort?: AbortController) => update(key, { total, abort }),
+  clear: (key: string) => update(key, null),
+  update: (key: string, progress: number, total?: number) => {
+    const download: Partial = { progress };
+    if (total !== undefined) {
+      download.total = total;
+    }
+    update(key, download);
+  },
+};
diff --git a/web/src/lib/utils/asset-utils.ts b/web/src/lib/utils/asset-utils.ts
index d420d9f5..55169acf 100644
--- a/web/src/lib/utils/asset-utils.ts
+++ b/web/src/lib/utils/asset-utils.ts
@@ -1,5 +1,5 @@
 import { notificationController, NotificationType } from '$lib/components/shared-components/notification/notification';
-import { clearDownload, updateDownload } from '$lib/stores/download';
+import { downloadManager } from '$lib/stores/download';
 import { AddAssetsResponseDto, api, AssetApiGetDownloadInfoRequest, AssetResponseDto, DownloadResponseDto } from '@api';
 import { handleError } from './handle-error';
 
@@ -37,7 +37,6 @@ const downloadBlob = (data: Blob, filename: string) => {
 export const downloadArchive = async (
   fileName: string,
   options: Omit,
-  onDone?: () => void,
   key?: string,
 ) => {
   let downloadInfo: DownloadResponseDto | null = null;
@@ -58,65 +57,77 @@ export const downloadArchive = async (
     const suffix = downloadInfo.archives.length === 1 ? '' : `+${i + 1}`;
     const archiveName = fileName.replace('.zip', `${suffix}.zip`);
 
-    let downloadKey = `${archiveName}`;
+    let downloadKey = `${archiveName} `;
     if (downloadInfo.archives.length > 1) {
       downloadKey = `${archiveName} (${i + 1}/${downloadInfo.archives.length})`;
     }
 
-    updateDownload(downloadKey, 0);
+    const abort = new AbortController();
+    downloadManager.add(downloadKey, archive.size, abort);
 
     try {
       const { data } = await api.assetApi.downloadArchive(
         { assetIdsDto: { assetIds: archive.assetIds }, key },
         {
           responseType: 'blob',
-          onDownloadProgress: (event) => updateDownload(downloadKey, Math.floor((event.loaded / archive.size) * 100)),
+          signal: abort.signal,
+          onDownloadProgress: (event) => downloadManager.update(downloadKey, event.loaded),
         },
       );
 
       downloadBlob(data, archiveName);
     } catch (e) {
       handleError(e, 'Unable to download files');
-      clearDownload(downloadKey);
+      downloadManager.clear(downloadKey);
       return;
     } finally {
-      setTimeout(() => clearDownload(downloadKey), 3_000);
+      setTimeout(() => downloadManager.clear(downloadKey), 5_000);
     }
   }
-
-  onDone?.();
 };
 
 export const downloadFile = async (asset: AssetResponseDto, key?: string) => {
-  const assets = [{ filename: `${asset.originalFileName}.${getFilenameExtension(asset.originalPath)}`, id: asset.id }];
+  const assets = [
+    {
+      filename: `${asset.originalFileName}.${getFilenameExtension(asset.originalPath)}`,
+      id: asset.id,
+      size: asset.exifInfo?.fileSizeInByte || 0,
+    },
+  ];
   if (asset.livePhotoVideoId) {
     assets.push({
       filename: `${asset.originalFileName}.mov`,
       id: asset.livePhotoVideoId,
+      size: 0,
     });
   }
 
-  for (const asset of assets) {
+  for (const { filename, id, size } of assets) {
+    const downloadKey = filename;
+
     try {
-      updateDownload(asset.filename, 0);
+      const abort = new AbortController();
+      downloadManager.add(downloadKey, size, abort);
 
       const { data } = await api.assetApi.downloadFile(
-        { id: asset.id, key },
+        { id, key },
         {
           responseType: 'blob',
           onDownloadProgress: (event: ProgressEvent) => {
             if (event.lengthComputable) {
-              updateDownload(asset.filename, Math.floor((event.loaded / event.total) * 100));
+              downloadManager.update(downloadKey, event.loaded, event.total);
             }
           },
+          signal: abort.signal,
         },
       );
 
-      downloadBlob(data, asset.filename);
+      downloadBlob(data, filename);
     } catch (e) {
-      handleError(e, `Error downloading ${asset.filename}`);
+      handleError(e, `Error downloading ${filename}`);
+      downloadManager.clear(downloadKey);
     } finally {
-      setTimeout(() => clearDownload(asset.filename), 3_000);
+      setTimeout(() => downloadManager.clear(downloadKey), 5_000);
     }
   }
 };
diff --git a/web/src/lib/utils/handle-error.ts b/web/src/lib/utils/handle-error.ts
index 3c9c0e1c..32a73f03 100644
--- a/web/src/lib/utils/handle-error.ts
+++ b/web/src/lib/utils/handle-error.ts
@@ -1,7 +1,12 @@
 import type { ApiError } from '@api';
+import { CanceledError } from 'axios';
 import { notificationController, NotificationType } from '../components/shared-components/notification/notification';
 
 export async function handleError(error: unknown, message: string) {
+  if (error instanceof CanceledError) {
+    return;
+  }
+
   console.error(`[handleError]: ${message}`, error);
 
   let data = (error as ApiError)?.response?.data;