diff --git a/mobile/lib/infrastructure/repositories/storage.repository.dart b/mobile/lib/infrastructure/repositories/storage.repository.dart index 164fa04529..3de2b0f32f 100644 --- a/mobile/lib/infrastructure/repositories/storage.repository.dart +++ b/mobile/lib/infrastructure/repositories/storage.repository.dart @@ -36,7 +36,8 @@ class StorageRepository { try { final entity = await AssetEntity.fromId(asset.id); - file = await entity?.originFileWithSubtype; + // Use a timeout to avoid hanging on Live Photos whose paired video is unavailable (e.g., shared albums) + file = await entity?.originFileWithSubtype.timeout(const Duration(seconds: 5)); if (file == null) { log.warning( "Cannot get motion file for asset ${asset.id}, name: ${asset.name}, created on: ${asset.createdAt}", diff --git a/mobile/lib/services/upload.service.dart b/mobile/lib/services/upload.service.dart index e8e98562f7..2c83699745 100644 --- a/mobile/lib/services/upload.service.dart +++ b/mobile/lib/services/upload.service.dart @@ -303,6 +303,7 @@ class UploadService { } File? file; + bool uploadAsLive = entity.isLivePhoto; /// iOS LivePhoto has two files: a photo and a video. /// They are uploaded separately, with video file being upload first, then returned with the assetId @@ -314,8 +315,14 @@ class UploadService { /// The cancel operation will only cancel the video group (normal group), the photo group will not /// be touched, as the video file is already uploaded. - if (entity.isLivePhoto) { + if (uploadAsLive) { file = await _storageRepository.getMotionFileForAsset(asset); + // Fallback: if motion video is unavailable (e.g., shared album or old format), upload still photo only + if (file == null) { + _logger.warning('Live Photo motion file missing for ${asset.id} (${asset.name}); uploading still image only'); + file = await _storageRepository.getFileForAsset(asset.id); + uploadAsLive = false; + } } else { file = await _storageRepository.getFileForAsset(asset.id); } @@ -324,11 +331,11 @@ class UploadService { return null; } - final originalFileName = entity.isLivePhoto ? p.setExtension(asset.name, p.extension(file.path)) : asset.name; + final originalFileName = uploadAsLive ? p.setExtension(asset.name, p.extension(file.path)) : asset.name; String metadata = UploadTaskMetadata( localAssetId: asset.id, - isLivePhotos: entity.isLivePhoto, + isLivePhotos: uploadAsLive, livePhotoVideoId: '', ).toJson();