Skip to content
Draft
Show file tree
Hide file tree
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
14 changes: 13 additions & 1 deletion media_kit_test/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,25 @@ class PrimaryScreen extends StatelessWidget {
valueListenable: configuration,
builder: (context, value, _) => TextButton(
onPressed: () {
configuration.value = VideoControllerConfiguration(
configuration.value = value.copyWith(
enableHardwareAcceleration: !value.enableHardwareAcceleration,
);
},
child: Text(value.enableHardwareAcceleration ? 'H/W' : 'S/W'),
),
),
if (UniversalPlatform.isAndroid)
ValueListenableBuilder<VideoControllerConfiguration>(
valueListenable: configuration,
builder: (context, value, _) => TextButton(
onPressed: () {
configuration.value = value.copyWith(
usePlatformView: !value.usePlatformView,
);
},
child: Text(value.usePlatformView ? 'PlatformView' : 'TextureView'),
),
),
const SizedBox(width: 16.0),
],
),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
* This file is a part of media_kit (https://github.com/media-kit/media-kit).
* <p>
* Copyright © 2021 & onwards, Hitesh Kumar Saini <[email protected]>.
* All rights reserved.
* Use of this source code is governed by MIT license that can be found in the LICENSE file.
*/
package com.alexmercerind.media_kit_video;

import android.util.Log;

import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Locale;
import java.util.Objects;

/**
* Manages global object references through JNI.
* This class provides functionality to create and delete global references to Java objects
* for use in native code.
*/
public class GlobalObjectRefManager {
private static final String TAG = "GlobalObjectRefManager";
private static final Method newGlobalObjectRefMethod;
private static final Method deleteGlobalObjectRefMethod;
private static final HashSet<Long> deletedGlobalObjectRefs = new HashSet<>();

static {
try {
// com.alexmercerind.mediakitandroidhelper.MediaKitAndroidHelper is part of package:media_kit_libs_android_video & package:media_kit_libs_android_audio packages.
// Use reflection to invoke methods of com.alexmercerind.mediakitandroidhelper.MediaKitAndroidHelper.
Class<?> mediaKitAndroidHelperClass = Class.forName("com.alexmercerind.mediakitandroidhelper.MediaKitAndroidHelper");
newGlobalObjectRefMethod = mediaKitAndroidHelperClass.getDeclaredMethod("newGlobalObjectRef", Object.class);
deleteGlobalObjectRefMethod = mediaKitAndroidHelperClass.getDeclaredMethod("deleteGlobalObjectRef", long.class);
newGlobalObjectRefMethod.setAccessible(true);
deleteGlobalObjectRefMethod.setAccessible(true);
} catch (Throwable e) {
Log.i("media_kit", "package:media_kit_libs_android_video missing. Make sure you have added it to pubspec.yaml.");
throw new RuntimeException("Failed to initialize com.alexmercerind.media_kit_video.GlobalObjectRefManager.", e);
}
}

/**
* Creates a new global reference to the given object.
*
* @param object The object to create a global reference for.
* @return The global reference ID, or 0 if creation failed.
*/
public static long newGlobalObjectRef(Object object) {
Log.i(TAG, String.format(Locale.ENGLISH, "newGlobalRef: object = %s", object));
try {
return (long) Objects.requireNonNull(newGlobalObjectRefMethod.invoke(null, object));
} catch (Throwable e) {
Log.e(TAG, "newGlobalRef", e);
return 0;
}
}

/**
* Deletes a global reference by its ID.
* This method tracks deleted references to prevent double deletion.
*
* @param ref The global reference ID to delete.
*/
public static void deleteGlobalObjectRef(long ref) {
if (deletedGlobalObjectRefs.contains(ref)) {
Log.i(TAG, String.format(Locale.ENGLISH, "deleteGlobalObjectRef: ref = %d ALREADY DELETED", ref));
return;
}
if (deletedGlobalObjectRefs.size() > 100) {
deletedGlobalObjectRefs.clear();
}
deletedGlobalObjectRefs.add(ref);
Log.i(TAG, String.format(Locale.ENGLISH, "deleteGlobalObjectRef: ref = %d", ref));
try {
deleteGlobalObjectRefMethod.invoke(null, ref);
} catch (Throwable e) {
Log.e(TAG, "deleteGlobalObjectRef", e);
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.platform.PlatformViewRegistry;

import com.alexmercerind.media_kit_video.platformview.PlatformVideoViewFactory;

/**
* MediaKitVideoPlugin
Expand All @@ -31,6 +34,12 @@ public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBindin

videoOutputManager = new VideoOutputManager(flutterPluginBinding.getTextureRegistry());

// Register PlatformViewFactory for PlatformView support
PlatformViewRegistry registry = flutterPluginBinding.getPlatformViewRegistry();
registry.registerViewFactory(
"com.alexmercerind/media_kit_video_platform_view",
new PlatformVideoViewFactory(channel)
);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,14 @@
import android.os.Looper;
import android.util.Log;

import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Locale;
import java.util.Objects;

import io.flutter.view.TextureRegistry;

public class VideoOutput implements TextureRegistry.SurfaceProducer.Callback {
private static final String TAG = "VideoOutput";
private static final Method newGlobalObjectRef;
private static final Method deleteGlobalObjectRef;
private static final HashSet<Long> deletedGlobalObjectRefs = new HashSet<>();
private static final Handler handler = new Handler(Looper.getMainLooper());

static {
try {
// com.alexmercerind.mediakitandroidhelper.MediaKitAndroidHelper is part of package:media_kit_libs_android_video & package:media_kit_libs_android_audio packages.
// Use reflection to invoke methods of com.alexmercerind.mediakitandroidhelper.MediaKitAndroidHelper.
Class<?> mediaKitAndroidHelperClass = Class.forName("com.alexmercerind.mediakitandroidhelper.MediaKitAndroidHelper");
newGlobalObjectRef = mediaKitAndroidHelperClass.getDeclaredMethod("newGlobalObjectRef", Object.class);
deleteGlobalObjectRef = mediaKitAndroidHelperClass.getDeclaredMethod("deleteGlobalObjectRef", long.class);
newGlobalObjectRef.setAccessible(true);
deleteGlobalObjectRef.setAccessible(true);
} catch (Throwable e) {
Log.i("media_kit", "package:media_kit_libs_android_video missing. Make sure you have added it to pubspec.yaml.");
throw new RuntimeException("Failed to initialize com.alexmercerind.media_kit_video.VideoOutput.");
}
}

private long id = 0;
private long wid = 0;

Expand Down Expand Up @@ -93,49 +72,22 @@ private void setSurfaceSize(int width, int height, boolean force) {
@Override
public void onSurfaceAvailable() {
synchronized (lock) {
Log.i(TAG, "onSurfaceAvailable");
Log.i(TAG, "onSurfaceAvailable: id=" + id + ", wid=" + wid + ", width=" + surfaceProducer.getWidth() + ", height=" + surfaceProducer.getHeight());
id = surfaceProducer.id();
wid = newGlobalObjectRef(surfaceProducer.getSurface());
wid = GlobalObjectRefManager.newGlobalObjectRef(surfaceProducer.getSurface());
textureUpdateCallback.onTextureUpdate(id, wid, surfaceProducer.getWidth(), surfaceProducer.getHeight());
}
}

@Override
public void onSurfaceCleanup() {
synchronized (lock) {
Log.i(TAG, "onSurfaceCleanup");
Log.i(TAG, "onSurfaceCleanup: id=" + id + ", wid=" + wid + ", width=" + surfaceProducer.getWidth() + ", height=" + surfaceProducer.getHeight());
textureUpdateCallback.onTextureUpdate(id, 0, surfaceProducer.getWidth(), surfaceProducer.getHeight());
if (wid != 0) {
final long widReference = wid;
handler.postDelayed(() -> deleteGlobalObjectRef(widReference), 5000);
handler.postDelayed(() -> GlobalObjectRefManager.deleteGlobalObjectRef(widReference), 5000);
}
}
}

private static long newGlobalObjectRef(Object object) {
Log.i(TAG, String.format(Locale.ENGLISH, "newGlobalRef: object = %s", object));
try {
return (long) Objects.requireNonNull(newGlobalObjectRef.invoke(null, object));
} catch (Throwable e) {
Log.e(TAG, "newGlobalRef", e);
return 0;
}
}

private static void deleteGlobalObjectRef(long ref) {
if (deletedGlobalObjectRefs.contains(ref)) {
Log.i(TAG, String.format(Locale.ENGLISH, "deleteGlobalObjectRef: ref = %d ALREADY DELETED", ref));
return;
}
if (deletedGlobalObjectRefs.size() > 100) {
deletedGlobalObjectRefs.clear();
}
deletedGlobalObjectRefs.add(ref);
Log.i(TAG, String.format(Locale.ENGLISH, "deleteGlobalObjectRef: ref = %d", ref));
try {
deleteGlobalObjectRef.invoke(null, ref);
} catch (Throwable e) {
Log.e(TAG, "deleteGlobalObjectRef", e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/**
* This file is a part of media_kit (https://github.com/media-kit/media-kit).
* <p>
* Copyright © 2021 & onwards, Hitesh Kumar Saini <[email protected]>.
* All rights reserved.
* Use of this source code is governed by MIT license that can be found in the LICENSE file.
*/
package com.alexmercerind.media_kit_video.platformview;

import java.util.function.Consumer;

import android.content.Context;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;

import androidx.annotation.NonNull;

import io.flutter.plugin.platform.PlatformView;

import com.alexmercerind.media_kit_video.GlobalObjectRefManager;
/**
* A class used to create a native video view that can be embedded in a Flutter app.
* It wraps a SurfaceView and connects it to libmpv.
*/
public final class PlatformVideoView implements PlatformView {
private static final String TAG = "PlatformVideoView";
private static final Handler handler = new Handler(Looper.getMainLooper());
@NonNull
private final SurfaceView surfaceView;
private final long handle;
private final int width;
private final int height;
private long wid = 0;
private final Consumer<Long> onSurfaceAvailable;

/**
* Constructs a new PlatformVideoView.
*
* @param context The context in which the view is running.
* @param handle The handle (player ID) of the video player.
* @param width The width of the video.
* @param height The height of the video.
* @param onSurfaceAvailable The callback to be called when the Surface is available.
*/
public PlatformVideoView(
@NonNull Context context,
long handle,
int width,
int height,
@NonNull Consumer<Long> onSurfaceAvailable) {
this.handle = handle;
this.width = width;
this.height = height;
this.onSurfaceAvailable = onSurfaceAvailable;
this.surfaceView = new SurfaceView(context);

if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1) {
// Avoid blank space instead of a video on Android versions below 8 by adjusting video's
// z-layer within the Android view hierarchy:
surfaceView.setZOrderMediaOverlay(true);
}

setupSurface();
}

private void setupSurface() {
surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
Log.i(TAG, "surfaceCreated: handle=" + handle + ", width=" + width + ", height=" + height);
if (holder.getSurface() != null) {
// Clean up old wid if it exists
if (wid != 0) {
Log.i(TAG, "surfaceCreated: cleaning up old wid=" + wid);
GlobalObjectRefManager.deleteGlobalObjectRef(wid);
wid = 0;
}
// Get global reference to the Surface only once when it's first created
wid = GlobalObjectRefManager.newGlobalObjectRef(holder.getSurface());
Log.i(TAG, "surfaceCreated: created new wid=" + wid);
holder.setFixedSize(width, height);
// Notify Dart side about the PlatformView Surface availability
onSurfaceAvailable.accept(wid);
}
}

@Override
public void surfaceChanged(
@NonNull SurfaceHolder holder, int format, int width, int height) {
Log.i(TAG, String.format("surfaceChanged: handle=%d, width=%d, height=%d, wid=%d", handle, width, height, wid));
}

@Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
Log.i(TAG, "surfaceDestroyed: handle=" + handle + ", wid=" + wid);
onSurfaceAvailable.accept(0L);
if (wid != 0) {
final long widReference = wid;
handler.postDelayed(() -> GlobalObjectRefManager.deleteGlobalObjectRef(widReference), 5000);
wid = 0;
}
}
});
}

/**
* Returns the view associated with this PlatformView.
*
* @return The SurfaceView used to display the video.
*/
@NonNull
@Override
public View getView() {
return surfaceView;
}

/** Disposes of the resources used by this PlatformView. */
@Override
public void dispose() {
Log.i(TAG, "dispose: handle=" + handle);
if (surfaceView.getHolder().getSurface() != null) {
surfaceView.getHolder().getSurface().release();
}
}
}

Loading
Loading