Browse Source

Merge pull request #86 from ente-io/livePhotoBug

Fix bug during first load of remote livePhoto
Vishnu Mohandas 3 years ago
parent
commit
9c3e73e617
2 changed files with 123 additions and 56 deletions
  1. 52 27
      lib/ui/zoomable_live_image.dart
  2. 71 29
      lib/utils/file_util.dart

+ 52 - 27
lib/ui/zoomable_live_image.dart

@@ -3,6 +3,7 @@ import 'package:chewie/chewie.dart';
 import 'package:flutter/foundation.dart';
 import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/widgets.dart';
 import 'package:flutter/widgets.dart';
+import 'package:fluttertoast/fluttertoast.dart';
 import 'package:logging/logging.dart';
 import 'package:logging/logging.dart';
 import 'package:photos/core/constants.dart';
 import 'package:photos/core/constants.dart';
 import 'package:photos/models/file.dart';
 import 'package:photos/models/file.dart';
@@ -13,13 +14,14 @@ import 'package:shared_preferences/shared_preferences.dart';
 import 'package:video_player/video_player.dart';
 import 'package:video_player/video_player.dart';
 
 
 class ZoomableLiveImage extends StatefulWidget {
 class ZoomableLiveImage extends StatefulWidget {
-  final File photo;
+  final File file;
   final Function(bool) shouldDisableScroll;
   final Function(bool) shouldDisableScroll;
   final String tagPrefix;
   final String tagPrefix;
   final Decoration backgroundDecoration;
   final Decoration backgroundDecoration;
 
 
+
   ZoomableLiveImage(
   ZoomableLiveImage(
-    this.photo, {
+    this.file, {
     Key key,
     Key key,
     this.shouldDisableScroll,
     this.shouldDisableScroll,
     @required this.tagPrefix,
     @required this.tagPrefix,
@@ -33,16 +35,16 @@ class ZoomableLiveImage extends StatefulWidget {
 class _ZoomableLiveImageState extends State<ZoomableLiveImage>
 class _ZoomableLiveImageState extends State<ZoomableLiveImage>
     with SingleTickerProviderStateMixin {
     with SingleTickerProviderStateMixin {
   final Logger _logger = Logger("ZoomableLiveImage");
   final Logger _logger = Logger("ZoomableLiveImage");
-  File _livePhoto;
-  bool _loadLivePhotoVideo = false;
+  File _file;
+  bool _showVideo = false;
+  bool _isLoadingVideoPlayer = false;
 
 
   VideoPlayerController _videoPlayerController;
   VideoPlayerController _videoPlayerController;
   ChewieController _chewieController;
   ChewieController _chewieController;
 
 
   @override
   @override
   void initState() {
   void initState() {
-    _livePhoto = widget.photo;
-    _loadLiveVideo();
+    _file = widget.file;
     _showLivePhotoToast();
     _showLivePhotoToast();
     super.initState();
     super.initState();
   }
   }
@@ -54,7 +56,7 @@ class _ZoomableLiveImageState extends State<ZoomableLiveImage>
     }
     }
     if (mounted) {
     if (mounted) {
       setState(() {
       setState(() {
-        _loadLivePhotoVideo = isPressed;
+        _showVideo = isPressed;
       });
       });
     }
     }
   }
   }
@@ -62,10 +64,15 @@ class _ZoomableLiveImageState extends State<ZoomableLiveImage>
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     Widget content;
     Widget content;
-    if (_loadLivePhotoVideo && _videoPlayerController != null) {
+    // check is long press is selected but videoPlayer is not configured yet
+    if (_showVideo && _videoPlayerController == null) {
+      _loadLiveVideo();
+    }
+
+    if (_showVideo && _videoPlayerController != null) {
       content = _getVideoPlayer();
       content = _getVideoPlayer();
     } else {
     } else {
-      content = ZoomableImage(_livePhoto,
+      content = ZoomableImage(_file,
           tagPrefix: widget.tagPrefix,
           tagPrefix: widget.tagPrefix,
           shouldDisableScroll: widget.shouldDisableScroll,
           shouldDisableScroll: widget.shouldDisableScroll,
           backgroundDecoration: widget.backgroundDecoration);
           backgroundDecoration: widget.backgroundDecoration);
@@ -95,29 +102,45 @@ class _ZoomableLiveImageState extends State<ZoomableLiveImage>
         aspectRatio: _videoPlayerController.value.aspectRatio,
         aspectRatio: _videoPlayerController.value.aspectRatio,
         autoPlay: true,
         autoPlay: true,
         autoInitialize: true,
         autoInitialize: true,
-        looping: false,
+        looping: true,
         allowFullScreen: false,
         allowFullScreen: false,
         showControls: false);
         showControls: false);
     return Chewie(controller: _chewieController);
     return Chewie(controller: _chewieController);
   }
   }
 
 
-  void _loadLiveVideo() {
-    // todo: add wrapper to download file from server if local is missing
-    getFile(widget.photo, liveVideo: true).then((file) {
-      if (file != null && file.existsSync()) {
-        _logger.fine("loading  from local");
-        _setVideoPlayerController(file: file);
-      } else if (widget.photo.uploadedFileID != null) {
-        _logger.fine("loading from remote");
-        getFileFromServer(widget.photo, liveVideo: true).then((file) {
-          if (file != null && file.existsSync()) {
-            _setVideoPlayerController(file: file);
-          } else {
-            _logger.warning("failed to load from remote" + widget.photo.tag());
-          }
-        });
-      }
+  Future<void> _loadLiveVideo() async {
+    // do nothing is already loading or loaded
+    if (_isLoadingVideoPlayer || _videoPlayerController != null) {
+      return;
+    }
+    _isLoadingVideoPlayer = true;
+    if (_file.isRemoteFile() && !(await isFileCached(_file, liveVideo: true))) {
+      showToast("downloading...", toastLength: Toast.LENGTH_LONG);
+    }
+
+    var videoFile = await getFile(widget.file, liveVideo: true)
+        .timeout(Duration(seconds: 15))
+        .onError((e, s) {
+      _logger.info("getFile failed ${_file.tag()}", e);
+      return null;
     });
     });
+
+    if ((videoFile == null || !videoFile.existsSync()) &&
+        _file.isRemoteFile()) {
+      videoFile = await getFileFromServer(widget.file, liveVideo: true)
+          .timeout(Duration(seconds: 15))
+          .onError((e, s) {
+        _logger.info("getRemoteFile failed ${_file.tag()}", e);
+        return null;
+      });
+    }
+
+    if (videoFile != null && videoFile.existsSync()) {
+      _setVideoPlayerController(file: videoFile);
+    } else {
+      showToast("download failed", toastLength: Toast.LENGTH_SHORT);
+    }
+    _isLoadingVideoPlayer = false;
   }
   }
 
 
   VideoPlayerController _setVideoPlayerController({io.File file}) {
   VideoPlayerController _setVideoPlayerController({io.File file}) {
@@ -125,7 +148,9 @@ class _ZoomableLiveImageState extends State<ZoomableLiveImage>
     return _videoPlayerController = videoPlayerController
     return _videoPlayerController = videoPlayerController
       ..initialize().whenComplete(() {
       ..initialize().whenComplete(() {
         if (mounted) {
         if (mounted) {
-          setState(() {});
+          setState(() {
+            _showVideo = true;
+          });
         }
         }
       });
       });
   }
   }

+ 71 - 29
lib/utils/file_util.dart

@@ -98,8 +98,8 @@ void preloadThumbnail(ente.File file) {
   }
   }
 }
 }
 
 
-final Map<int, Future<io.File>> fileDownloadsInProgress =
-    Map<int, Future<io.File>>();
+final Map<String, Future<io.File>> fileDownloadsInProgress =
+    <String, Future<io.File>>{};
 
 
 Future<io.File> getFileFromServer(
 Future<io.File> getFileFromServer(
   ente.File file, {
   ente.File file, {
@@ -109,31 +109,66 @@ Future<io.File> getFileFromServer(
   final cacheManager = (file.fileType == FileType.video || liveVideo)
   final cacheManager = (file.fileType == FileType.video || liveVideo)
       ? VideoCacheManager.instance
       ? VideoCacheManager.instance
       : DefaultCacheManager();
       : DefaultCacheManager();
-  return cacheManager.getFileFromCache(file.getDownloadUrl()).then((info) {
-    if (info == null) {
-      if (!fileDownloadsInProgress.containsKey(file.uploadedFileID)) {
-        if (file.fileType == FileType.livePhoto) {
-          fileDownloadsInProgress[file.uploadedFileID] = _downloadLivePhoto(
-              file,
-              progressCallback: progressCallback,
-              liveVideo: liveVideo);
-        } else {
-          fileDownloadsInProgress[file.uploadedFileID] = _downloadAndCache(
-            file,
-            cacheManager,
-            progressCallback: progressCallback,
-          );
-        }
-      }
-      return fileDownloadsInProgress[file.uploadedFileID];
+  final fileFromCache =
+      await cacheManager.getFileFromCache(file.getDownloadUrl());
+  if (fileFromCache != null) {
+    return fileFromCache.file;
+  }
+  final downloadID = file.uploadedFileID.toString() + liveVideo.toString();
+  if (!fileDownloadsInProgress.containsKey(downloadID)) {
+    if (file.fileType == FileType.livePhoto) {
+      fileDownloadsInProgress[downloadID] = _getLivePhotoFromServer(file,
+              progressCallback: progressCallback, needLiveVideo: liveVideo)
+          .whenComplete(() {
+        fileDownloadsInProgress.remove(downloadID);
+      });
     } else {
     } else {
-      return info.file;
+      fileDownloadsInProgress[downloadID] = _downloadAndCache(
+              file, cacheManager,
+              progressCallback: progressCallback)
+          .whenComplete(() {
+        fileDownloadsInProgress.remove(downloadID);
+      });
     }
     }
-  });
+  }
+  return fileDownloadsInProgress[downloadID];
 }
 }
 
 
-Future<io.File> _downloadLivePhoto(ente.File file,
-    {ProgressCallback progressCallback, bool liveVideo = false}) async {
+Future<bool> isFileCached(ente.File file,
+    {bool liveVideo = false}) async {
+  final cacheManager = (file.fileType == FileType.video || liveVideo)
+      ? VideoCacheManager.instance
+      : DefaultCacheManager();
+  final fileInfo = await cacheManager.getFileFromCache(file.getDownloadUrl());
+  return fileInfo != null;
+}
+
+final Map<int, Future<_LivePhoto>> livePhotoDownloadsTracker =
+    <int, Future<_LivePhoto>>{};
+
+Future<io.File> _getLivePhotoFromServer(ente.File file,
+    {ProgressCallback progressCallback, bool needLiveVideo}) async {
+  final downloadID = file.uploadedFileID;
+  try {
+    if (!livePhotoDownloadsTracker.containsKey(downloadID)) {
+      livePhotoDownloadsTracker[downloadID] =
+          _downloadLivePhoto(file, progressCallback: progressCallback);
+    }
+    final livePhoto = await livePhotoDownloadsTracker[file.uploadedFileID];
+    livePhotoDownloadsTracker.remove(downloadID);
+    if (livePhoto == null) {
+      return null;
+    }
+    return needLiveVideo ? livePhoto.video : livePhoto.image;
+  } catch (e,s) {
+    _logger.warning("live photo get failed", e, s);
+    livePhotoDownloadsTracker.remove(downloadID);
+    return null;
+  }
+}
+
+Future<_LivePhoto> _downloadLivePhoto(ente.File file,
+    {ProgressCallback progressCallback}) async {
   return downloadAndDecrypt(file, progressCallback: progressCallback)
   return downloadAndDecrypt(file, progressCallback: progressCallback)
       .then((decryptedFile) async {
       .then((decryptedFile) async {
     if (decryptedFile == null) {
     if (decryptedFile == null) {
@@ -189,11 +224,11 @@ Future<io.File> _downloadLivePhoto(ente.File file,
         }
         }
       }
       }
     }
     }
-    fileDownloadsInProgress.remove(file.uploadedFileID);
-    return liveVideo ? videoFileCache : imageFileCache;
+    return _LivePhoto(imageFileCache, videoFileCache);
   }).catchError((e) {
   }).catchError((e) {
-    fileDownloadsInProgress.remove(file.uploadedFileID);
-    _logger.warning("failed to download live photos" + e.toString());
+    _logger.warning(
+        "failed to download live photos : ${file.tag()}", e);
+    throw e;
   });
   });
 }
 }
 
 
@@ -224,10 +259,10 @@ Future<io.File> _downloadAndCache(ente.File file, BaseCacheManager cacheManager,
       fileExtension: fileExtension,
       fileExtension: fileExtension,
     );
     );
     await outputFile.delete();
     await outputFile.delete();
-    fileDownloadsInProgress.remove(file.uploadedFileID);
     return cachedFile;
     return cachedFile;
   }).catchError((e) {
   }).catchError((e) {
-    fileDownloadsInProgress.remove(file.uploadedFileID);
+    _logger.warning("failed to download file : ${file.tag()}", e);
+    throw e;
   });
   });
 }
 }
 
 
@@ -264,3 +299,10 @@ Future<void> clearCache(ente.File file) async {
     await cachedThumbnail.delete();
     await cachedThumbnail.delete();
   }
   }
 }
 }
+
+class _LivePhoto {
+  final io.File image;
+  final io.File video;
+
+  _LivePhoto(this.image, this.video);
+}