Fix iOS hang when selecting many photos

Root cause: FileReader.readAsDataURL() loads the entire file into memory
as a base64 string. With 50+ iPhone photos (3-12 MB each), this exceeds
iOS Safari's per-tab memory limit and freezes the tab.

Changes:
- Replace readAsDataURL with URL.createObjectURL(file) — zero-copy file
  reference, no memory bloat (static/index.html)
- Reduce batch size from 4 to 2 — gentler on memory-constrained devices
- Add URL.revokeObjectURL() on reset — prevent blob URL leaks
- Add HEIC/HEIF support to server.py — iPhone format compatibility

Closes #1
This commit is contained in:
2026-05-25 16:20:04 -04:00
parent c1516471ac
commit 0052c59f81
9 changed files with 21 additions and 10 deletions
+11 -8
View File
@@ -304,9 +304,9 @@ document.getElementById('fileInput').addEventListener('change', async function(e
'</div>'
).join('');
// Scan all in parallel (limited to 4 at a time to avoid memory issues)
// Scan in small batches — iOS Safari can't handle many concurrent file reads
const results = [];
const batchSize = 4;
const batchSize = 2;
for (let i = 0; i < files.length; i += batchSize) {
const batch = files.slice(i, i + batchSize);
const batchResults = await Promise.all(batch.map(f => scanPhoto(f)));
@@ -321,12 +321,9 @@ document.getElementById('fileInput').addEventListener('change', async function(e
async function scanPhoto(file) {
const result = { file, exif: null, hasGps: false, lat: null, lng: null, thumb: null };
// Generate thumbnail
result.thumb = await new Promise(resolve => {
const reader = new FileReader();
reader.onload = e => resolve(e.target.result);
reader.readAsDataURL(file);
});
// Use object URL instead of readAsDataURL — avoids loading the entire file
// into memory as a base64 string (the main cause of iOS hangs with many photos)
result.thumb = URL.createObjectURL(file);
// Read EXIF
try {
@@ -726,6 +723,12 @@ async function pushGpsToAsset(idx) {
}
function resetAll() {
// Revoke all blob URLs to prevent memory leaks
allPhotos.forEach(p => {
if (p.thumb && p.thumb.startsWith('blob:')) {
URL.revokeObjectURL(p.thumb);
}
});
allPhotos = [];
selectedIdx = -1;
currentFilter = 'all';