zoomable_live_image.dart 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. // @dart=2.9
  2. import 'dart:io' as io;
  3. import 'package:chewie/chewie.dart';
  4. import 'package:flutter/material.dart';
  5. import 'package:fluttertoast/fluttertoast.dart';
  6. import 'package:logging/logging.dart';
  7. import 'package:photos/core/constants.dart';
  8. import 'package:photos/models/file.dart';
  9. import 'package:photos/ui/viewer/file/zoomable_image.dart';
  10. import 'package:photos/utils/file_util.dart';
  11. import 'package:photos/utils/toast_util.dart';
  12. import 'package:shared_preferences/shared_preferences.dart';
  13. import 'package:video_player/video_player.dart';
  14. class ZoomableLiveImage extends StatefulWidget {
  15. final File file;
  16. final Function(bool) shouldDisableScroll;
  17. final String tagPrefix;
  18. final Decoration backgroundDecoration;
  19. const ZoomableLiveImage(
  20. this.file, {
  21. Key key,
  22. this.shouldDisableScroll,
  23. @required this.tagPrefix,
  24. this.backgroundDecoration,
  25. }) : super(key: key);
  26. @override
  27. State<ZoomableLiveImage> createState() => _ZoomableLiveImageState();
  28. }
  29. class _ZoomableLiveImageState extends State<ZoomableLiveImage>
  30. with SingleTickerProviderStateMixin {
  31. final Logger _logger = Logger("ZoomableLiveImage");
  32. File _file;
  33. bool _showVideo = false;
  34. bool _isLoadingVideoPlayer = false;
  35. VideoPlayerController _videoPlayerController;
  36. ChewieController _chewieController;
  37. @override
  38. void initState() {
  39. _file = widget.file;
  40. _showLivePhotoToast();
  41. super.initState();
  42. }
  43. void _onLongPressEvent(bool isPressed) {
  44. if (_videoPlayerController != null && isPressed == false) {
  45. // stop playing video
  46. _videoPlayerController.pause();
  47. }
  48. if (mounted) {
  49. setState(() {
  50. _showVideo = isPressed;
  51. });
  52. }
  53. }
  54. @override
  55. Widget build(BuildContext context) {
  56. Widget content;
  57. // check is long press is selected but videoPlayer is not configured yet
  58. if (_showVideo && _videoPlayerController == null) {
  59. _loadLiveVideo();
  60. }
  61. if (_showVideo && _videoPlayerController != null) {
  62. content = _getVideoPlayer();
  63. } else {
  64. content = ZoomableImage(
  65. _file,
  66. tagPrefix: widget.tagPrefix,
  67. shouldDisableScroll: widget.shouldDisableScroll,
  68. backgroundDecoration: widget.backgroundDecoration,
  69. );
  70. }
  71. return GestureDetector(
  72. onLongPressStart: (_) => {_onLongPressEvent(true)},
  73. onLongPressEnd: (_) => {_onLongPressEvent(false)},
  74. child: content,
  75. );
  76. }
  77. @override
  78. void dispose() {
  79. if (_videoPlayerController != null) {
  80. _videoPlayerController.pause();
  81. _videoPlayerController.dispose();
  82. }
  83. if (_chewieController != null) {
  84. _chewieController.dispose();
  85. }
  86. super.dispose();
  87. }
  88. Widget _getVideoPlayer() {
  89. _videoPlayerController.seekTo(Duration.zero);
  90. _chewieController = ChewieController(
  91. videoPlayerController: _videoPlayerController,
  92. aspectRatio: _videoPlayerController.value.aspectRatio,
  93. autoPlay: true,
  94. autoInitialize: true,
  95. looping: true,
  96. allowFullScreen: false,
  97. showControls: false,
  98. );
  99. return Container(
  100. color: Colors.black,
  101. child: Chewie(controller: _chewieController), // same for both theme
  102. );
  103. }
  104. Future<void> _loadLiveVideo() async {
  105. // do nothing is already loading or loaded
  106. if (_isLoadingVideoPlayer || _videoPlayerController != null) {
  107. return;
  108. }
  109. _isLoadingVideoPlayer = true;
  110. if (_file.isRemoteFile() && !(await isFileCached(_file, liveVideo: true))) {
  111. showToast(context, "Downloading...", toastLength: Toast.LENGTH_LONG);
  112. }
  113. var videoFile = await getFile(widget.file, liveVideo: true)
  114. .timeout(const Duration(seconds: 15))
  115. .onError((e, s) {
  116. _logger.info("getFile failed ${_file.tag()}", e);
  117. return null;
  118. });
  119. // FixMe: Here, we are fetching video directly when getFile failed
  120. // getFile with liveVideo as true can fail for file with localID when
  121. // the live photo was downloaded from remote.
  122. if ((videoFile == null || !videoFile.existsSync()) &&
  123. _file.uploadedFileID != null) {
  124. videoFile = await getFileFromServer(widget.file, liveVideo: true)
  125. .timeout(const Duration(seconds: 15))
  126. .onError((e, s) {
  127. _logger.info("getRemoteFile failed ${_file.tag()}", e);
  128. return null;
  129. });
  130. }
  131. if (videoFile != null && videoFile.existsSync()) {
  132. _setVideoPlayerController(file: videoFile);
  133. } else {
  134. showShortToast(context, "Download failed");
  135. }
  136. _isLoadingVideoPlayer = false;
  137. }
  138. VideoPlayerController _setVideoPlayerController({io.File file}) {
  139. final videoPlayerController = VideoPlayerController.file(file);
  140. return _videoPlayerController = videoPlayerController
  141. ..initialize().whenComplete(() {
  142. if (mounted) {
  143. setState(() {
  144. _showVideo = true;
  145. });
  146. }
  147. });
  148. }
  149. void _showLivePhotoToast() async {
  150. final preferences = await SharedPreferences.getInstance();
  151. final int promptTillNow = preferences.getInt(livePhotoToastCounterKey) ?? 0;
  152. if (promptTillNow < maxLivePhotoToastCount && mounted) {
  153. showToast(context, "Press and hold to play video");
  154. preferences.setInt(livePhotoToastCounterKey, promptTillNow + 1);
  155. }
  156. }
  157. }