Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -55,15 +51,21 @@ public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {

@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) {
final Map<String, Object> args = call.arguments();

final String video = (String) args.get("video");
final HashMap<String, String> headers = (HashMap<String, String>) 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<String, String> 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() {
Expand All @@ -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;
}
Expand All @@ -92,18 +93,60 @@ 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<String, String> asStringMap(Object v) {
if (!(v instanceof Map)) return null;
final Map<?, ?> raw = (Map<?, ?>) v;
final HashMap<String, String> 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:
return Bitmap.CompressFormat.JPEG;
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:
Expand All @@ -116,28 +159,35 @@ private static String formatExt(int format) {
}
}

private byte[] buildThumbnailData(final String vidPath, final HashMap<String, String> 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<String, String> 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<String, String> 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<String, String> 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;
Expand All @@ -151,9 +201,7 @@ private String buildThumbnailFile(final String vidPath, final HashMap<String, St
if (path.endsWith(ext)) {
fullpath = path;
} else {
// try to save to same folder as the vidPath
final int j = fullpath.lastIndexOf("/");

if (path.endsWith("/")) {
fullpath = path + fullpath.substring(j + 1);
} else {
Expand All @@ -167,7 +215,7 @@ private String buildThumbnailFile(final String vidPath, final HashMap<String, St
f.write(bytes);
f.close();
Log.d(TAG, String.format("buildThumbnailFile( written:%d )", bytes.length));
} catch (java.io.IOException e) {
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
Expand Down Expand Up @@ -198,16 +246,7 @@ private static void runOnUiThread(Runnable runnable) {
new Handler(Looper.getMainLooper()).post(runnable);
}

/**
* Create a video thumbnail for a video. May return null if the video is corrupt
* or the format is not supported.
*
* @param video the URI of video
* @param targetH the max height of the thumbnail
* @param targetW the max width of the thumbnail
*/
public Bitmap createVideoThumbnail(final String video, final HashMap<String, String> headers, int targetH,
int targetW, int timeMs) {
public Bitmap createVideoThumbnail(final String video, final HashMap<String, String> headers, int targetH, int targetW, int timeMs) {
Bitmap bitmap = null;
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
try {
Expand All @@ -221,11 +260,9 @@ public Bitmap createVideoThumbnail(final String video, final HashMap<String, Str

if (targetH != 0 || targetW != 0) {
if (android.os.Build.VERSION.SDK_INT >= 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();
Expand All @@ -240,7 +277,7 @@ public Bitmap createVideoThumbnail(final String video, final HashMap<String, Str
}
}
} else {
bitmap = retriever.getFrameAtTime(timeMs * 1000, MediaMetadataRetriever.OPTION_CLOSEST);
bitmap = retriever.getFrameAtTime(timeMs * 1000L, MediaMetadataRetriever.OPTION_CLOSEST);
}
} catch (IllegalArgumentException ex) {
ex.printStackTrace();
Expand All @@ -251,7 +288,7 @@ public Bitmap createVideoThumbnail(final String video, final HashMap<String, Str
} finally {
try {
retriever.release();
} catch (RuntimeException | IOException ex) {
} catch (Throwable ex) {
ex.printStackTrace();
}
}
Expand Down