我正在尝试将 Android 实现添加到 https://github.com/jfversluis/Plugin.Maui.ScreenRecording
我想我检查了所有基础,但我在 MediaRecorder.Stop(); 上遇到了这个异常
{Java.Lang.RuntimeException: stop failed.
at Java.Interop.JniEnvironment.InstanceMethods.CallVoidMethod(JniObjectReference instance, JniMethodInfo method, JniArgumentValue* args) in /Users/runner/work/1/s/xamarin-android/external/Java.Interop/src/Java.Interop/obj/Release/net7.0/JniEnvironment.g.cs:line 20370
at Java.Interop.JniPeerMembers.JniInstanceMethods.InvokeVirtualVoidMethod(String encodedMember, IJavaPeerable self, JniArgumentValue* parameters) in /Users/runner/work/1/s/xamarin-android/external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods_Invoke.cs:line 66
at Android.Media.MediaRecorder.Stop() in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/obj/Release/net8.0/android-34/mcw/Android.Media.MediaRecorder.cs:line 2477
at Plugin.Maui.ScreenRecording.ScreenRecordingImplementation.StopRecording(ScreenRecordingOptions options) in D:\pmsr\src\Plugin.Maui.ScreenRecording\ScreenRecording.android.cs:line 84
--- End of managed Java.Lang.RuntimeException stack trace ---
java.lang.RuntimeException: stop failed.
at android.media.MediaRecorder.stop(Native Method)
at crc64fcf28c0e24b4cc31.ButtonHandler_ButtonClickListener.n_onClick(Native Method)
at crc64fcf28c0e24b4cc31.ButtonHandler_ButtonClickListener.onClick(ButtonHandler_ButtonClickListener.java:31)
at android.view.View.performClick(View.java:7448)
at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1211)
at android.view.View.performClickInternal(View.java:7425)
at android.view.View.access$3600(View.java:810)
at android.view.View$PerformClick.run(View.java:28305)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
--- End of managed Java.Lang.RuntimeException stack trace ---
java.lang.RuntimeException: stop failed.
at android.media.MediaRecorder.stop(Native Method)
at crc64fcf28c0e24b4cc31.ButtonHandler_ButtonClickListener.n_onClick(Native Method)
at crc64fcf28c0e24b4cc31.ButtonHandler_ButtonClickListener.onClick(ButtonHandler_ButtonClickListener.java:31)
at android.view.View.performClick(View.java:7448)
at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1211)
at android.view.View.performClickInternal(View.java:7425)
at android.view.View.access$3600(View.java:810)
at android.view.View$PerformClick.run(View.java:28305)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
}
我猜我正在寻找一些愚蠢的东西。任何指导或建议将不胜感激。
ScreenRecording.android.cs
using Android.Content;
using Android.Media;
using Android.Media.Projection;
using Android.Hardware.Display;
using Android.Views;
using Microsoft.Maui.ApplicationModel;
using Android.Content.PM;
using Android.OS;
using AndroidX.Core.App;
using Android.Graphics.Drawables;
using AndroidX.Core.Graphics.Drawable;
using Microsoft.Maui.Devices;
using static Android.Provider.Telephony.Mms;
using Microsoft.Maui.Storage;
namespace Plugin.Maui.ScreenRecording;
public partial class ScreenRecordingImplementation : IScreenRecording
{
public MediaProjectionManager ProjectionManager;
public MediaProjection MediaProjection;
public VirtualDisplay VirtualDisplay;
public MediaRecorder MediaRecorder;
public string FilePath;
public const int REQUEST_MEDIA_PROJECTION = 1;
public static EventHandler<ScreenRecordingEventArgs> ScreenRecordingPermissionHandler;
public ScreenRecordingImplementation()
{
ProjectionManager = (MediaProjectionManager)Platform.AppContext.GetSystemService(Context.MediaProjectionService);
}
public bool IsRecording { get; private set; }
public bool IsSupported { get { return ProjectionManager != null; } private set { }}
public async Task StartRecording(bool enableMicrophone)
{
if (IsSupported)
{
MediaRecorder = new MediaRecorder();
//FilePath = Path.Combine(Platform.AppContext.FilesDir.AbsolutePath, $"recording_{DateTime.Now:yyyyMMdd_HHmmss}.mp4");
FilePath = Path.Combine(FileSystem.Current.CacheDirectory, "Screen.mp4");
SetUpMediaRecorder(enableMicrophone);
try
{
await Task.Delay(2000);
MediaRecorder.Start();
IsRecording = true;
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
throw new NotSupportedException("Screen recording did not start.");
}
}
else
{
throw new NotSupportedException("Screen recording not supported on this device.");
}
}
public async Task<ScreenRecordingFile?> StopRecording(ScreenRecordingOptions? options)
{
if (IsRecording)
{
IsRecording = false;
try
{
MediaRecorder.Stop();
MediaRecorder.Release();
VirtualDisplay.Release();
MediaProjection.Stop();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
var context = Android.App.Application.Context;
context.StopService(new Intent(context, typeof(ScreenRecordingService)));
return new ScreenRecordingFile(FilePath);
}
return null;
}
public async void OnScreenCapturePermissionGranted(Result resultCode, Intent? data)
{
if (resultCode == Result.Ok && data != null)
{
var context = Android.App.Application.Context;
Intent serviceIntent = new Intent(context, typeof(ScreenRecordingService));
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
{
context.StartForegroundService(serviceIntent);
}
else
{
context.StartService(serviceIntent);
}
await Task.Delay(2000);
// Handle the successful permission result
MediaProjection mediaProjection = ProjectionManager.GetMediaProjection((int)resultCode, data);
VirtualDisplay = mediaProjection.CreateVirtualDisplay("Plugin.Maui.ScreenRecording.ScreenRecordingImplementation.ScreenRecordingService", (int)DeviceDisplay.Current.MainDisplayInfo.Width, (int)DeviceDisplay.Current.MainDisplayInfo.Height, (int)DeviceDisplay.Current.MainDisplayInfo.Density, Android.Views.DisplayFlags.Presentation, null, null, null);
}
else
{
// Handle the case where permission was not granted
}
// Additional setup or start recording
}
public void SetUpMediaRecorder(bool enableMicrophone)
{
MediaRecorder = new MediaRecorder();
if (enableMicrophone)
{
MediaRecorder.SetAudioSource(AudioSource.Mic);
}
MediaRecorder.SetVideoSource(VideoSource.Surface);
MediaRecorder.SetOutputFormat(OutputFormat.Mpeg4);
MediaRecorder.SetVideoEncoder(VideoEncoder.H264);
if (enableMicrophone)
{
MediaRecorder.SetAudioEncoder(AudioEncoder.AmrNb);
}
MediaRecorder.SetOutputFile(FilePath);
MediaRecorder.SetVideoSize((int)DeviceDisplay.Current.MainDisplayInfo.Width, (int)DeviceDisplay.Current.MainDisplayInfo.Height);
MediaRecorder.SetVideoFrameRate(30);
try
{
MediaRecorder.Prepare();
}
catch (Java.IO.IOException ex)
{
Console.WriteLine($"MediaRecorder preparation failed: {ex.Message}");
// Handle preparation failure
MediaRecorder.Release();
}
}
public void Setup()
{
Intent captureIntent = ProjectionManager.CreateScreenCaptureIntent();
Platform.CurrentActivity.StartActivityForResult(captureIntent, REQUEST_MEDIA_PROJECTION);
}
public class ScreenRecordingEventArgs : EventArgs
{
public Result ResultCode { get; set; }
public Intent? Data { get; set; }
}
[Service(ForegroundServiceType = ForegroundService.TypeMediaProjection)]
public class ScreenRecordingService : Service
{
public override IBinder? OnBind(Intent? intent)
{
return null;
}
public override StartCommandResult OnStartCommand(Intent? intent, StartCommandFlags flags, int startId)
{
string CHANNEL_ID = "ScreenRecordingService";
int NOTIFICATION_ID = 1337;
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
{
var channelName = "Screen Recording Service";
var channel = new NotificationChannel(CHANNEL_ID, channelName, NotificationImportance.Default)
{
Description = "Notification Channel for Screen Recording Service"
};
var notificationManager = (NotificationManager)GetSystemService(NotificationService);
notificationManager.CreateNotificationChannel(channel);
}
// Create a notification for the foreground service
var notificationBuilder = new Notification.Builder(this, CHANNEL_ID)
.SetContentTitle("Screen Recording")
.SetContentText("Recording screen...")
.SetSmallIcon(Resource.Drawable.notification_template_icon_low_bg); // Ensure you have 'ic_notification' in Resources/drawable
var notification = notificationBuilder.Build();
// Start the service in the foreground
StartForeground(NOTIFICATION_ID, notification);
return StartCommandResult.Sticky;
}
}
}
MainActivity.cs
using Android.App;
using Android.Content.PM;
using Android.OS;
using Plugin.Maui.ScreenRecording;
namespace ScreenRecordingSample;
[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
public class MainActivity : MauiAppCompatActivity
{
public const int REQUEST_MEDIA_PROJECTION = 1;
ScreenRecordingImplementation screenRecordingImplementation = new ScreenRecordingImplementation();
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// Initialize other components...
// Initialize ScreenRecordingImplementation
screenRecordingImplementation = new Plugin.Maui.ScreenRecording.ScreenRecordingImplementation();
}
protected override void OnActivityResult(int requestCode, Result resultCode, Android.Content.Intent? data)
{
base.OnActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_MEDIA_PROJECTION)
{
screenRecordingImplementation.OnScreenCapturePermissionGranted(resultCode, data);
}
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:allowBackup="true" android:supportsRtl="true">
<service android:name="Plugin.Maui.ScreenRecording.ScreenRecordingImplementation.ScreenRecordingService" android:foregroundServiceType="mediaProjection" />
</application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-sdk />
</manifest>
IScreenRecording.cs
namespace Plugin.Maui.ScreenRecording;
/// <summary>
/// Provides the ability to record the screen within your app.
/// </summary>
public interface IScreenRecording
{
/// <summary>
/// Gets whether or not screen recording is currently happening.
/// </summary>
bool IsRecording { get; }
/// <summary>
/// Gets whether or not screen recording is supported on this device.
/// </summary>
bool IsSupported { get; }
/// <summary>
/// Sets up the screenrecording.
/// </summary>
/// <remarks>
/// This is needed for android to request media projection.
/// </remarks>
void Setup();
/// <summary>
/// Starts the screenrecording.
/// </summary>
/// <param name="enableMicrophone">Determines if the microphone should be used as input during the recording.</param>
/// <returns>A <see cref="Task"/> object with information about this operation.</returns>
/// <remarks>
/// A permission to access the microphone might be needed, this is not requested by this method.
/// Make sure to add an entry in the metadata of your app and request the runtime permission
/// before calling this method.
/// </remarks>
Task StartRecording(bool enableMicrophone);
/// <summary>
/// Stops the recording and saves the video file to the device's gallery.
/// </summary>
/// <returns>A <see cref="Task"/> object with information about this operation.</returns>
Task<ScreenRecordingFile?> StopRecording(ScreenRecordingOptions? options = null);
}
其余代码与我认为的主存储库相同。
MediaRecorder.Stop()
调用,以优雅地处理任何运行时异常。这不能解决根本原因,但可以防止应用程序崩溃。
try
{
MediaRecorder.Stop();
}
catch (Java.Lang.RuntimeException ex)
{
Console.WriteLine("Error stopping MediaRecorder: " + ex.Message);
// Handle the exception, e.g., by logging
}
如果录制尚未实际开始或已停止,则
MediaRecorder.Stop()
方法将失败。在调用 MediaRecorder.Start()
之前,请确保 Stop()
已成功调用。
if (IsRecording)
{
// Proceed to stop the recording
}
MediaRecorder
类对于方法调用的顺序也非常敏感。确保 Prepare()
、Start()
和 Stop()
调用的顺序正确。您必须在 Prepare()
之前致电 Start()
,并且在致电 Start()
之前,Stop()
必须有足够的时间启动录音。
如果录制开始后立即停止,则
MediaRecorder
可能没有足够的数据来完成视频文件,从而导致此异常。在启动和停止之间引入延迟,以确保记录一些数据。
// Wait for a minimum duration before allowing stop
if ((DateTime.UtcNow - recordingStartTime).TotalSeconds < MIN_RECORDING_DURATION)
{
// Wait or notify the user
}
停止录制后,请确保资源已正确释放。在
MediaRecorder.Release()
之后致电 MediaRecorder.Stop()
。另外,释放 VirtualDisplay
和 MediaProjection
对象。