You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Maui录屏插件Android实现:MediaRecorder.Stop()抛出RuntimeException

Android屏幕录制MediaRecorder.Stop()抛出stop failed异常的解决建议

针对你遇到的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 12:05:54