Merge pull request #86 from ente-io/livePhotoBug
Fix bug during first load of remote livePhoto
This commit is contained in:
commit
9c3e73e617
2 changed files with 123 additions and 56 deletions
|
@ -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;
|
File _file;
|
||||||
bool _loadLivePhotoVideo = false;
|
bool _showVideo = false;
|
||||||
|
bool _isLoadingVideoPlayer = false;
|
||||||
|
|
||||||
VideoPlayerController _videoPlayerController;
|
VideoPlayerController _videoPlayerController;
|
||||||
ChewieController _chewieController;
|
ChewieController _chewieController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
_livePhoto = widget.photo;
|
_file = widget.file;
|
||||||
_loadLiveVideo();
|
|
||||||
_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() {
|
Future<void> _loadLiveVideo() async {
|
||||||
// todo: add wrapper to download file from server if local is missing
|
// do nothing is already loading or loaded
|
||||||
getFile(widget.photo, liveVideo: true).then((file) {
|
if (_isLoadingVideoPlayer || _videoPlayerController != null) {
|
||||||
if (file != null && file.existsSync()) {
|
return;
|
||||||
_logger.fine("loading from local");
|
}
|
||||||
_setVideoPlayerController(file: file);
|
_isLoadingVideoPlayer = true;
|
||||||
} else if (widget.photo.uploadedFileID != null) {
|
if (_file.isRemoteFile() && !(await isFileCached(_file, liveVideo: true))) {
|
||||||
_logger.fine("loading from remote");
|
showToast("downloading...", toastLength: Toast.LENGTH_LONG);
|
||||||
getFileFromServer(widget.photo, liveVideo: true).then((file) {
|
}
|
||||||
if (file != null && file.existsSync()) {
|
|
||||||
_setVideoPlayerController(file: file);
|
var videoFile = await getFile(widget.file, liveVideo: true)
|
||||||
} else {
|
.timeout(Duration(seconds: 15))
|
||||||
_logger.warning("failed to load from remote" + widget.photo.tag());
|
.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;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,8 +98,8 @@ void preloadThumbnail(ente.File file) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final Map<int, Future<io.File>> fileDownloadsInProgress =
|
final Map<String, Future<io.File>> fileDownloadsInProgress =
|
||||||
Map<int, Future<io.File>>();
|
<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) {
|
final fileFromCache =
|
||||||
if (info == null) {
|
await cacheManager.getFileFromCache(file.getDownloadUrl());
|
||||||
if (!fileDownloadsInProgress.containsKey(file.uploadedFileID)) {
|
if (fileFromCache != null) {
|
||||||
if (file.fileType == FileType.livePhoto) {
|
return fileFromCache.file;
|
||||||
fileDownloadsInProgress[file.uploadedFileID] = _downloadLivePhoto(
|
}
|
||||||
file,
|
final downloadID = file.uploadedFileID.toString() + liveVideo.toString();
|
||||||
progressCallback: progressCallback,
|
if (!fileDownloadsInProgress.containsKey(downloadID)) {
|
||||||
liveVideo: liveVideo);
|
if (file.fileType == FileType.livePhoto) {
|
||||||
} else {
|
fileDownloadsInProgress[downloadID] = _getLivePhotoFromServer(file,
|
||||||
fileDownloadsInProgress[file.uploadedFileID] = _downloadAndCache(
|
progressCallback: progressCallback, needLiveVideo: liveVideo)
|
||||||
file,
|
.whenComplete(() {
|
||||||
cacheManager,
|
fileDownloadsInProgress.remove(downloadID);
|
||||||
progressCallback: progressCallback,
|
});
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fileDownloadsInProgress[file.uploadedFileID];
|
|
||||||
} 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,
|
Future<bool> isFileCached(ente.File file,
|
||||||
{ProgressCallback progressCallback, bool liveVideo = false}) async {
|
{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 _LivePhoto(imageFileCache, videoFileCache);
|
||||||
return liveVideo ? videoFileCache : imageFileCache;
|
|
||||||
}).catchError((e) {
|
}).catchError((e) {
|
||||||
fileDownloadsInProgress.remove(file.uploadedFileID);
|
_logger.warning(
|
||||||
_logger.warning("failed to download live photos" + e.toString());
|
"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);
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue