Maui录屏插件Android实现:MediaRecorder.Stop()抛出RuntimeException
针对你遇到的Java.Lang.RuntimeException: stop failed异常,结合代码分析,核心问题集中在MediaRecorder与VirtualDisplay的关联、资源初始化/释放顺序、权限处理等方面,以下是具体修复方案:
1. 修复MediaRecorder与VirtualDisplay的关联问题
当前代码中,VirtualDisplay创建时未传入MediaRecorder的Surface,导致MediaRecorder没有视频输入源,这是Stop()失败的核心原因。
修改步骤:
- 在类中新增Surface字段存储MediaRecorder的输出Surface
- 在MediaRecorder准备完成后获取Surface
- 创建VirtualDisplay时传入该Surface
代码修改:
// 在ScreenRecordingImplementation类中添加字段 public Surface? MediaRecorderSurface; // 修改SetUpMediaRecorder方法 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(); // 保存MediaRecorder的Surface MediaRecorderSurface = MediaRecorder.Surface; } catch (Java.IO.IOException ex) { Console.WriteLine($"MediaRecorder preparation failed: {ex.Message}"); MediaRecorder.Release(); MediaRecorderSurface = null; } } // 修改OnScreenCapturePermissionGranted中的VirtualDisplay创建代码 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, MediaRecorderSurface, // 传入MediaRecorder的Surface null, null);
2. 修复MediaRecorder重复实例化问题
当前StartRecording中先实例化MediaRecorder,SetUpMediaRecorder里又重新实例化,导致实例状态混乱。
修改StartRecording方法:
public async Task StartRecording(bool enableMicrophone) { if (IsSupported) { // 用唯一文件名避免覆盖旧文件 FilePath = Path.Combine(FileSystem.Current.CacheDirectory, $"recording_{DateTime.Now:yyyyMMdd_HHmmss}.mp4"); SetUpMediaRecorder(enableMicrophone); if (MediaRecorderSurface == null) { throw new InvalidOperationException("MediaRecorder初始化失败,无法获取Surface"); } try { await Task.Delay(500); // 缩短不必要的延迟 MediaRecorder.Start(); IsRecording = true; } catch(Exception ex) { Console.WriteLine(ex.Message); MediaRecorder?.Release(); MediaRecorderSurface = null; throw new NotSupportedException("屏幕录制无法启动。"); } } else { throw new NotSupportedException("当前设备不支持屏幕录制。"); } }
3. 修复资源释放顺序错误
停止录制时,需先切断视频源(释放VirtualDisplay),再停止MediaRecorder,最后释放MediaProjection,否则MediaRecorder会因数据中断抛出异常。
修改StopRecording方法:
public async Task<ScreenRecordingFile?> StopRecording(ScreenRecordingOptions? options) { if (IsRecording) { IsRecording = false; try { // 先释放VirtualDisplay,让MediaRecorder处理完剩余数据 VirtualDisplay?.Release(); await Task.Delay(300); // 等待数据写入完成 MediaRecorder?.Stop(); MediaRecorder?.Release(); MediaProjection?.Stop(); // 重置字段 MediaRecorder = null; MediaRecorderSurface = null; VirtualDisplay = null; MediaProjection = null; } catch (Exception ex) { Console.WriteLine(ex.Message); // 确保资源被强制释放 MediaRecorder?.Release(); VirtualDisplay?.Release(); MediaProjection?.Stop(); } var context = Android.App.Application.Context; context.StopService(new Intent(context, typeof(ScreenRecordingService))); return new ScreenRecordingFile(FilePath); } return null; }
4. 修复MediaProjection字段未赋值问题
当前OnScreenCapturePermissionGranted中创建的MediaProjection是局部变量,未赋值给类字段,导致StopRecording时调用MediaProjection.Stop()会引发空引用异常。
修改OnScreenCapturePermissionGranted方法:
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(500); // 将创建的MediaProjection赋值给类字段 MediaProjection = ProjectionManager.GetMediaProjection((int)resultCode, data); if (MediaRecorderSurface == null) { Console.WriteLine("MediaRecorder Surface未初始化,无法创建VirtualDisplay"); return; } 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, MediaRecorderSurface, null, null); } else { Console.WriteLine("屏幕录制权限未授予"); } }
5. 补充麦克风权限检查
启用麦克风录制时,需确保已获取RECORD_AUDIO权限,否则MediaRecorder初始化会失败。
在StartRecording中添加权限检查:
public async Task StartRecording(bool enableMicrophone) { if (IsSupported) { if (enableMicrophone) { var status = await Permissions.CheckStatusAsync<Permissions.Microphone>(); if (status != PermissionStatus.Granted) { status = await Permissions.RequestAsync<Permissions.Microphone>(); if (status != PermissionStatus.Granted) { throw new PermissionException("麦克风权限未授予,无法启用音频录制"); } } } // 剩余代码... } }
6. 修复Notification图标问题
当前代码中使用的Resource.Drawable.notification_template_icon_low_bg可能不存在,导致前台服务启动失败。请替换为项目中已有的图标资源:
.SetSmallIcon(Resource.Drawable.ic_notification); // 确保Resources/drawable下有该图标文件
内容的提问来源于stack exchange,提问作者berr08

