feat(android,interface): Implement video thumbnail generation
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
<application>
|
||||
<provider
|
||||
android:name="me.polynom.moxplatform_android.MoxplatformFileProvider"
|
||||
android:authorities="me.polynom.moxplatform_android.fileprovider"
|
||||
android:authorities="me.polynom.moxplatform_android.fileprovider2"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
|
||||
@@ -1097,6 +1097,9 @@ public class Api {
|
||||
void decryptFile(@NonNull String sourcePath, @NonNull String destPath, @NonNull byte[] key, @NonNull byte[] iv, @NonNull CipherAlgorithm algorithm, @NonNull String hashSpec, @NonNull Result<CryptographyResult> result);
|
||||
|
||||
void hashFile(@NonNull String sourcePath, @NonNull String hashSpec, @NonNull Result<byte[]> result);
|
||||
/** Media APIs */
|
||||
@NonNull
|
||||
Boolean generateVideoThumbnail(@NonNull String src, @NonNull String dest, @NonNull Long maxWidth);
|
||||
/** Stubs */
|
||||
void eventStub(@NonNull NotificationEvent event);
|
||||
|
||||
@@ -1466,6 +1469,32 @@ public class Api {
|
||||
channel.setMessageHandler(null);
|
||||
}
|
||||
}
|
||||
{
|
||||
BasicMessageChannel<Object> channel =
|
||||
new BasicMessageChannel<>(
|
||||
binaryMessenger, "dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.generateVideoThumbnail", getCodec());
|
||||
if (api != null) {
|
||||
channel.setMessageHandler(
|
||||
(message, reply) -> {
|
||||
ArrayList<Object> wrapped = new ArrayList<Object>();
|
||||
ArrayList<Object> args = (ArrayList<Object>) message;
|
||||
String srcArg = (String) args.get(0);
|
||||
String destArg = (String) args.get(1);
|
||||
Number maxWidthArg = (Number) args.get(2);
|
||||
try {
|
||||
Boolean output = api.generateVideoThumbnail(srcArg, destArg, (maxWidthArg == null) ? null : maxWidthArg.longValue());
|
||||
wrapped.add(0, output);
|
||||
}
|
||||
catch (Throwable exception) {
|
||||
ArrayList<Object> wrappedError = wrapError(exception);
|
||||
wrapped = wrappedError;
|
||||
}
|
||||
reply.reply(wrapped);
|
||||
});
|
||||
} else {
|
||||
channel.setMessageHandler(null);
|
||||
}
|
||||
}
|
||||
{
|
||||
BasicMessageChannel<Object> channel =
|
||||
new BasicMessageChannel<>(
|
||||
|
||||
@@ -25,7 +25,7 @@ const val NOTIFICATION_EXTRA_ID_KEY = "notification_id"
|
||||
const val NOTIFICATION_MESSAGE_EXTRA_MIME = "mime"
|
||||
const val NOTIFICATION_MESSAGE_EXTRA_PATH = "path"
|
||||
|
||||
const val MOXPLATFORM_FILEPROVIDER_ID = "me.polynom.moxplatform_android.fileprovider"
|
||||
const val MOXPLATFORM_FILEPROVIDER_ID = "me.polynom.moxplatform_android.fileprovider2"
|
||||
|
||||
// Shared preferences keys
|
||||
const val SHARED_PREFERENCES_KEY = "me.polynom.moxplatform_android"
|
||||
|
||||
@@ -3,9 +3,11 @@ package me.polynom.moxplatform_android;
|
||||
import static android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS;
|
||||
import static androidx.core.content.ContextCompat.getSystemService;
|
||||
import static androidx.core.content.ContextCompat.startActivity;
|
||||
import static me.polynom.moxplatform_android.ConstantsKt.MOXPLATFORM_FILEPROVIDER_ID;
|
||||
import static me.polynom.moxplatform_android.ConstantsKt.SHARED_PREFERENCES_KEY;
|
||||
import static me.polynom.moxplatform_android.CryptoKt.*;
|
||||
import static me.polynom.moxplatform_android.RecordSentMessageKt.*;
|
||||
import static me.polynom.moxplatform_android.ThumbnailsKt.generateVideoThumbnailImplementation;
|
||||
|
||||
import me.polynom.moxplatform_android.Api.*;
|
||||
|
||||
@@ -13,20 +15,31 @@ import android.app.ActivityManager;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Bitmap;
|
||||
import android.media.MediaMetadataRetriever;
|
||||
import android.media.ThumbnailUtils;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.PowerManager;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.Log;
|
||||
import android.util.Size;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.content.FileProvider;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -313,6 +326,12 @@ public class MoxplatformAndroidPlugin extends BroadcastReceiver implements Flutt
|
||||
CryptoKt.hashFile(sourcePath, hashSpec, result);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Boolean generateVideoThumbnail(@NonNull String src, @NonNull String dest, @NonNull Long maxWidth) {
|
||||
return generateVideoThumbnailImplementation(src, dest, maxWidth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventStub(@NonNull NotificationEvent event) {
|
||||
// Stub to trick pigeon into
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package me.polynom.moxplatform_android
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.media.MediaMetadataRetriever
|
||||
import android.util.Log
|
||||
import java.io.FileOutputStream
|
||||
|
||||
/*
|
||||
* Generate a video thumbnail using the first frame of the video at @src. Afterwards, scale it
|
||||
* down such that its width is equal to @maxWidth (while keeping the aspect ratio) and write it to
|
||||
* @dest.
|
||||
*
|
||||
* If everything went well, returns true. If we're unable to generate the thumbnail, returns false.
|
||||
* */
|
||||
fun generateVideoThumbnailImplementation(src: String, dest: String, maxWidth: Long): Boolean {
|
||||
try {
|
||||
val mmr = MediaMetadataRetriever().apply {
|
||||
setDataSource(src)
|
||||
}
|
||||
val unscaledThumbnail = mmr.getFrameAtTime(0) ?: return false
|
||||
|
||||
// Scale down the thumbnail while keeping the aspect ratio
|
||||
val scalingFactor = maxWidth.toDouble() / unscaledThumbnail.width;
|
||||
Log.d(TAG, "Scaling to $maxWidth from ${unscaledThumbnail.width} with scalingFactor $scalingFactor");
|
||||
val thumbnail = Bitmap.createScaledBitmap(
|
||||
unscaledThumbnail,
|
||||
(unscaledThumbnail.width * scalingFactor).toInt(),
|
||||
(unscaledThumbnail.height * scalingFactor).toInt(),
|
||||
false,
|
||||
)
|
||||
|
||||
// Write it to the destination file
|
||||
val fos = FileOutputStream(dest)
|
||||
thumbnail.compress(Bitmap.CompressFormat.JPEG, 75, fos)
|
||||
fos.flush()
|
||||
fos.close()
|
||||
return true;
|
||||
} catch (ex: Exception) {
|
||||
Log.e(TAG, "Failed to create thumbnail for $src: ${ex.message}")
|
||||
ex.printStackTrace()
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user