diff --git a/android/src/main/java/xyz/justsoft/video_thumbnail/VideoThumbnailPlugin.java b/android/src/main/java/xyz/justsoft/video_thumbnail/VideoThumbnailPlugin.java index 9d4c097..5f92db4 100644 --- a/android/src/main/java/xyz/justsoft/video_thumbnail/VideoThumbnailPlugin.java +++ b/android/src/main/java/xyz/justsoft/video_thumbnail/VideoThumbnailPlugin.java @@ -12,7 +12,6 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.util.HashMap; @@ -26,9 +25,6 @@ import io.flutter.plugin.common.MethodChannel.MethodCallHandler; import io.flutter.plugin.common.MethodChannel.Result; -/** - * VideoThumbnailPlugin - */ public class VideoThumbnailPlugin implements FlutterPlugin, MethodCallHandler { private static String TAG = "ThumbnailPlugin"; private static final int HIGH_QUALITY_MIN_VAL = 70; @@ -55,15 +51,21 @@ public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { @Override public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) { - final Map args = call.arguments(); - - final String video = (String) args.get("video"); - final HashMap headers = (HashMap) args.get("headers"); - final int format = (int) args.get("format"); - final int maxh = (int) args.get("maxh"); - final int maxw = (int) args.get("maxw"); - final int timeMs = (int) args.get("timeMs"); - final int quality = (int) args.get("quality"); + final Object arguments = call.arguments; + if (!(arguments instanceof Map)) { + result.error("invalid_args", "Invalid arguments", null); + return; + } + + final Map args = (Map) arguments; + + final String video = asString(args.get("video")); + final HashMap headers = asStringMap(args.get("headers")); + final int format = asInt(args.get("format")); + final int maxh = asInt(args.get("maxh")); + final int maxw = asInt(args.get("maxw")); + final int timeMs = asInt(args.get("timeMs")); + final int quality = asInt(args.get("quality")); final String method = call.method; executor.execute(new Runnable() { @@ -74,12 +76,11 @@ public void run() { Exception exc = null; try { - if (method.equals("file")) { - final String path = (String) args.get("path"); + if ("file".equals(method)) { + final String path = asString(args.get("path")); thumbnail = buildThumbnailFile(video, headers, path, format, maxh, maxw, timeMs, quality); handled = true; - - } else if (method.equals("data")) { + } else if ("data".equals(method)) { thumbnail = buildThumbnailData(video, headers, format, maxh, maxw, timeMs, quality); handled = true; } @@ -92,7 +93,33 @@ public void run() { }); } - private static Bitmap.CompressFormat intToFormat(int format) { + private static String asString(Object v) { + return v == null ? null : String.valueOf(v); + } + + private static int asInt(Object v) { + if (v instanceof Number) return ((Number) v).intValue(); + if (v instanceof String) { + try { + return Integer.parseInt((String) v); + } catch (NumberFormatException ignored) { + } + } + return 0; + } + + private static HashMap asStringMap(Object v) { + if (!(v instanceof Map)) return null; + final Map raw = (Map) v; + final HashMap out = new HashMap<>(); + for (Map.Entry e : raw.entrySet()) { + if (e.getKey() == null || e.getValue() == null) continue; + out.put(String.valueOf(e.getKey()), String.valueOf(e.getValue())); + } + return out; + } + + private static Bitmap.CompressFormat intToFormat(int format, int quality) { switch (format) { default: case 0: @@ -100,10 +127,26 @@ private static Bitmap.CompressFormat intToFormat(int format) { case 1: return Bitmap.CompressFormat.PNG; case 2: - return Bitmap.CompressFormat.WEBP; + return webpCompressFormat(quality); } } + private static Bitmap.CompressFormat webpCompressFormat(int quality) { + if (android.os.Build.VERSION.SDK_INT >= 30) { + return quality >= HIGH_QUALITY_MIN_VAL + ? Bitmap.CompressFormat.WEBP_LOSSLESS + : Bitmap.CompressFormat.WEBP_LOSSY; + } + + try { + Object v = Bitmap.CompressFormat.class.getField("WEBP").get(null); + if (v instanceof Bitmap.CompressFormat) return (Bitmap.CompressFormat) v; + } catch (Throwable ignored) { + } + + return Bitmap.CompressFormat.PNG; + } + private static String formatExt(int format) { switch (format) { default: @@ -116,28 +159,35 @@ private static String formatExt(int format) { } } - private byte[] buildThumbnailData(final String vidPath, final HashMap headers, int format, int maxh, - int maxw, int timeMs, int quality) { - // Log.d(TAG, String.format("buildThumbnailData( format:%d, maxh:%d, maxw:%d, - // timeMs:%d, quality:%d )", format, maxh, maxw, timeMs, quality)); + private byte[] buildThumbnailData( + final String vidPath, + final HashMap headers, + int format, + int maxh, + int maxw, + int timeMs, + int quality + ) { Bitmap bitmap = createVideoThumbnail(vidPath, headers, maxh, maxw, timeMs); - if (bitmap == null) - throw new NullPointerException(); + if (bitmap == null) throw new NullPointerException(); ByteArrayOutputStream stream = new ByteArrayOutputStream(); - bitmap.compress(intToFormat(format), quality, stream); + bitmap.compress(intToFormat(format, quality), quality, stream); bitmap.recycle(); - if (bitmap == null) - throw new NullPointerException(); return stream.toByteArray(); } - private String buildThumbnailFile(final String vidPath, final HashMap headers, String path, - int format, int maxh, int maxw, int timeMs, - int quality) { - // Log.d(TAG, String.format("buildThumbnailFile( format:%d, maxh:%d, maxw:%d, - // timeMs:%d, quality:%d )", format, maxh, maxw, timeMs, quality)); - final byte bytes[] = buildThumbnailData(vidPath, headers, format, maxh, maxw, timeMs, quality); + private String buildThumbnailFile( + final String vidPath, + final HashMap headers, + String path, + int format, + int maxh, + int maxw, + int timeMs, + int quality + ) { + final byte[] bytes = buildThumbnailData(vidPath, headers, format, maxh, maxw, timeMs, quality); final String ext = formatExt(format); final int i = vidPath.lastIndexOf("."); String fullpath = vidPath.substring(0, i + 1) + ext; @@ -151,9 +201,7 @@ private String buildThumbnailFile(final String vidPath, final HashMap headers, int targetH, - int targetW, int timeMs) { + public Bitmap createVideoThumbnail(final String video, final HashMap headers, int targetH, int targetW, int timeMs) { Bitmap bitmap = null; MediaMetadataRetriever retriever = new MediaMetadataRetriever(); try { @@ -221,11 +260,9 @@ public Bitmap createVideoThumbnail(final String video, final HashMap= 27 && targetH != 0 && targetW != 0) { - // API Level 27 - bitmap = retriever.getScaledFrameAtTime(timeMs * 1000, MediaMetadataRetriever.OPTION_CLOSEST, - targetW, targetH); + bitmap = retriever.getScaledFrameAtTime(timeMs * 1000L, MediaMetadataRetriever.OPTION_CLOSEST, targetW, targetH); } else { - bitmap = retriever.getFrameAtTime(timeMs * 1000, MediaMetadataRetriever.OPTION_CLOSEST); + bitmap = retriever.getFrameAtTime(timeMs * 1000L, MediaMetadataRetriever.OPTION_CLOSEST); if (bitmap != null) { int width = bitmap.getWidth(); int height = bitmap.getHeight(); @@ -240,7 +277,7 @@ public Bitmap createVideoThumbnail(final String video, final HashMap