Pārlūkot izejas kodu

Merge pull request #48 from ente-io/support_sharing_video_thumbnail

Android: Support sharing videos to ente
Vishnu Mohandas 3 gadi atpakaļ
vecāks
revīzija
8553e96247

+ 12 - 0
android/app/src/main/AndroidManifest.xml

@@ -32,6 +32,18 @@
                 <data android:mimeType="image/*" />
                 <data android:mimeType="image/*" />
             </intent-filter>
             </intent-filter>
 
 
+            <intent-filter>
+                <action android:name="android.intent.action.SEND" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="video/*" />
+            </intent-filter>
+
+            <intent-filter>
+                <action android:name="android.intent.action.SEND_MULTIPLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="video/*" />
+            </intent-filter>
+
         </activity>
         </activity>
         <!-- Don't delete the meta-data below.
         <!-- Don't delete the meta-data below.
              This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
              This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->

+ 16 - 6
lib/ui/video_widget.dart

@@ -43,6 +43,14 @@ class _VideoWidgetState extends State<VideoWidget> {
     super.initState();
     super.initState();
     if (widget.file.isRemoteFile()) {
     if (widget.file.isRemoteFile()) {
       _loadNetworkVideo();
       _loadNetworkVideo();
+    } else if (widget.file.isSharedMediaToAppSandbox()) {
+      final localFile = io.File(getSharedMediaFilePath(widget.file));
+      if (localFile.existsSync()) {
+        _logger.fine("loading from app cache");
+        _setVideoPlayerController(file: localFile);
+      } else if (widget.file.uploadedFileID != null) {
+        _loadNetworkVideo();
+      }
     } else {
     } else {
       widget.file.getAsset().then((asset) async {
       widget.file.getAsset().then((asset) async {
         if (asset == null || !(await asset.exists)) {
         if (asset == null || !(await asset.exists)) {
@@ -62,12 +70,14 @@ class _VideoWidgetState extends State<VideoWidget> {
     getFileFromServer(
     getFileFromServer(
       widget.file,
       widget.file,
       progressCallback: (count, total) {
       progressCallback: (count, total) {
-        setState(() {
-          _progress = count / total;
-          if (_progress == 1) {
-            showToast("decrypting video...", toastLength: Toast.LENGTH_SHORT);
-          }
-        });
+        if (mounted) {
+          setState(() {
+            _progress = count / total;
+            if (_progress == 1) {
+              showToast("decrypting video...", toastLength: Toast.LENGTH_SHORT);
+            }
+          });
+        }
       },
       },
     ).then((file) {
     ).then((file) {
       if (file != null) {
       if (file != null) {

+ 20 - 2
lib/utils/file_uploader_util.dart

@@ -6,6 +6,7 @@ import 'package:archive/archive_io.dart';
 import 'package:logging/logging.dart';
 import 'package:logging/logging.dart';
 import 'package:motionphoto/motionphoto.dart';
 import 'package:motionphoto/motionphoto.dart';
 import 'package:path/path.dart';
 import 'package:path/path.dart';
+import 'package:path_provider/path_provider.dart';
 import 'package:photo_manager/photo_manager.dart';
 import 'package:photo_manager/photo_manager.dart';
 import 'package:photos/core/configuration.dart';
 import 'package:photos/core/configuration.dart';
 import 'package:photos/core/constants.dart';
 import 'package:photos/core/constants.dart';
@@ -13,6 +14,7 @@ import 'package:photos/core/errors.dart';
 import 'package:photos/models/file.dart' as ente;
 import 'package:photos/models/file.dart' as ente;
 import 'package:photos/models/file_type.dart';
 import 'package:photos/models/file_type.dart';
 import 'package:photos/models/location.dart';
 import 'package:photos/models/location.dart';
+import 'package:video_thumbnail/video_thumbnail.dart';
 
 
 import 'file_util.dart';
 import 'file_util.dart';
 
 
@@ -140,8 +142,14 @@ Future<MediaUploadData> _getMediaUploadDataFromAppCache(ente.File file) async {
     _logger.warning("File doesn't exist in app sandbox");
     _logger.warning("File doesn't exist in app sandbox");
     throw InvalidFileError("File doesn't exist in app sandbox");
     throw InvalidFileError("File doesn't exist in app sandbox");
   }
   }
-  thumbnailData = await getThumbnailFromInAppCacheFile(file);
-  return MediaUploadData(sourceFile, thumbnailData, isDeleted);
+  try {
+    thumbnailData = await getThumbnailFromInAppCacheFile(file);
+    return MediaUploadData(sourceFile, thumbnailData, isDeleted);
+  } catch (e, s) {
+    _logger.severe("failed to generate thumbnail", e, s);
+    throw InvalidFileError(
+        "thumbnail generation failed for fileType: ${file.fileType.toString()}");
+  }
 }
 }
 
 
 Future<Uint8List> getThumbnailFromInAppCacheFile(ente.File file) async {
 Future<Uint8List> getThumbnailFromInAppCacheFile(ente.File file) async {
@@ -149,6 +157,16 @@ Future<Uint8List> getThumbnailFromInAppCacheFile(ente.File file) async {
   if (!localFile.existsSync()) {
   if (!localFile.existsSync()) {
     return null;
     return null;
   }
   }
+  if (file.fileType == FileType.video) {
+    final thumbnailFilePath = await VideoThumbnail.thumbnailFile(
+      video: localFile.path,
+      imageFormat: ImageFormat.JPEG,
+      thumbnailPath: (await getTemporaryDirectory()).path,
+      maxWidth: kThumbnailLargeSize,
+      quality: 80,
+    );
+    localFile = io.File(thumbnailFilePath);
+  }
   var thumbnailData = await localFile.readAsBytes();
   var thumbnailData = await localFile.readAsBytes();
   int compressionAttempts = 0;
   int compressionAttempts = 0;
   while (thumbnailData.length > kThumbnailDataLimit &&
   while (thumbnailData.length > kThumbnailDataLimit &&

+ 26 - 15
lib/utils/share_util.dart

@@ -40,29 +40,40 @@ Future<List<File>> convertIncomingSharedMediaToFile(
     List<SharedMediaFile> sharedMedia, int collectionID) async {
     List<SharedMediaFile> sharedMedia, int collectionID) async {
   List<File> localFiles = [];
   List<File> localFiles = [];
   for (var media in sharedMedia) {
   for (var media in sharedMedia) {
+    if (!(media.type == SharedMediaType.IMAGE ||
+        media.type == SharedMediaType.VIDEO)) {
+      _logger.warning(
+          "ignore unsupported file type ${media.type.toString()} path: ${media.path}");
+      continue;
+    }
     var enteFile = File();
     var enteFile = File();
     // fileName: img_x.jpg
     // fileName: img_x.jpg
     enteFile.title = basename(media.path);
     enteFile.title = basename(media.path);
-
     var ioFile = dartio.File(media.path);
     var ioFile = dartio.File(media.path);
-    ioFile = ioFile.renameSync(Configuration.instance.getSharedMediaCacheDirectory() +
-        "/" +
-        enteFile.title);
+    ioFile = ioFile.renameSync(
+        Configuration.instance.getSharedMediaCacheDirectory() +
+            "/" +
+            enteFile.title);
     enteFile.localID = kSharedMediaIdentifier + enteFile.title;
     enteFile.localID = kSharedMediaIdentifier + enteFile.title;
     enteFile.collectionID = collectionID;
     enteFile.collectionID = collectionID;
-    enteFile.fileType = FileType.image;
+    enteFile.fileType =
+        media.type == SharedMediaType.IMAGE ? FileType.image : FileType.video;
 
 
-    var exifMap = await readExifFromFile(ioFile);
-    if (exifMap != null &&
-        exifMap["Image DateTime"] != null &&
-        '0000:00:00 00:00:00' != exifMap["Image DateTime"].toString()) {
-      try {
-        final exifTime =
-            _exifDateFormat.parse(exifMap["Image DateTime"].toString());
-        enteFile.creationTime = exifTime.microsecondsSinceEpoch;
-      } catch (e) {
-        //ignore
+    if (enteFile.fileType == FileType.image) {
+      final exifMap = await readExifFromFile(ioFile);
+      if (exifMap != null &&
+          exifMap["Image DateTime"] != null &&
+          '0000:00:00 00:00:00' != exifMap["Image DateTime"].toString()) {
+        try {
+          final exifTime =
+              _exifDateFormat.parse(exifMap["Image DateTime"].toString());
+          enteFile.creationTime = exifTime.microsecondsSinceEpoch;
+        } catch (e) {
+          //ignore
+        }
       }
       }
+    } else if (enteFile.fileType == FileType.video) {
+      enteFile.duration = media.duration ?? 0;
     }
     }
     if (enteFile.creationTime == null || enteFile.creationTime == 0) {
     if (enteFile.creationTime == null || enteFile.creationTime == 0) {
       final parsedDateTime =
       final parsedDateTime =

+ 7 - 0
pubspec.lock

@@ -1091,6 +1091,13 @@ packages:
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
     version: "2.0.0"
     version: "2.0.0"
+  video_thumbnail:
+    dependency: "direct main"
+    description:
+      name: video_thumbnail
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.4.3"
   visibility_detector:
   visibility_detector:
     dependency: "direct main"
     dependency: "direct main"
     description:
     description:

+ 1 - 0
pubspec.yaml

@@ -98,6 +98,7 @@ dependencies:
   url_launcher: ^6.0.3
   url_launcher: ^6.0.3
   uuid: ^3.0.4
   uuid: ^3.0.4
   video_player: ^2.0.0
   video_player: ^2.0.0
+  video_thumbnail: ^0.4.3
   visibility_detector: ^0.2.0
   visibility_detector: ^0.2.0
   wallpaper_manager_flutter: ^0.0.2
   wallpaper_manager_flutter: ^0.0.2