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

MAUI WebView应用麦克风权限被拒问题求助

MAUI WebView调用getUserMedia麦克风权限被拒问题

我在MAUI WebView应用中,通过JavaScript调用navigator.mediaDevices.getUserMedia访问麦克风时,持续收到"Permission Not Allowed"错误。已经在AndroidManifest.xml中配置了RECORD_AUDIO等相关权限,手机系统也已允许麦克风访问权限,但WebView无法像Android设备上的Chrome浏览器那样正常工作。

AndroidManifest权限配置

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" /> 
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> 
<uses-permission android:name="android.permission.MICROPHONE" /> 
<uses-permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

录音相关JavaScript代码

async function setRecordButtonListeners() {
    var myRecorder = {
        objects: {
            context: null,
            stream: null,
            recorder: null
        },
        init: function () {
            if (null === myRecorder.objects.context) {
                myRecorder.objects.context = new (
                    window.AudioContext || window.webkitAudioContext
                );
            }
        },
        start: function () {
            try {
                var options = { audio: true, video: false };
                console.log("Recording started....");
                navigator.mediaDevices.getUserMedia(options).then(function (stream) {
                    myRecorder.objects.stream = stream;
                    myRecorder.objects.recorder = new Recorder(
                        myRecorder.objects.context.createMediaStreamSource(stream),
                        { numChannels: 1 }
                    );
                    myRecorder.objects.recorder.record();
                }).catch(function (err) { });
                writeToOutput(err);
                setRecordTimer(myRecorder);
                playPopSound();
            }
            
        },
        stop: function (listObject) {
            if (null !== myRecorder.objects.stream) {
                myRecorder.objects.stream.getAudioTracks()[0].stop();
            }
            if (null !== myRecorder.objects.recorder) {
                myRecorder.objects.recorder.stop();

                // Validate object
                /*if (null !== listObject) {*/
                    // Export the WAV file
                    myRecorder.objects.recorder.exportWAV(async function (blob) {
                        var url = (window.URL || window.webkitURL)
                            .createObjectURL(blob);

                        // Prepare the playback
                        var audioObject = document.createElement("audio");
                        audioObject.setAttribute("controls", "");
                        audioObject.setAttribute("controlsList", "noremoteplayback");
                        audioObject.setAttribute('src', url);
                        console.log("Recording stopped....");
                        playPopSound();
                        //Prepare the upload link
                        var savedToServer = await postAudioRecording(blob);
                        //var messages = document.getElementById("messages");
                        
                    });
                }
            }
        }
    };

    // Prepare the recordings list
    var listObject = document.getElementById("recordingList");

    // Prepare the record button
    document.getElementById("audioRecordButton").addEventListener("click", function () {
        // Initialize the recorder
        myRecorder.init();

        // Get the button state 
        var buttonState = !!this.getAttribute('data-recording');

        // Toggle
        if (!buttonState) {
            this.setAttribute('data-recording', 'true');
            myRecorder.start();
        } else {
            this.setAttribute('data-recording', '');
            myRecorder.stop(listObject);
        }
    });
};

var recorderTimeout;

function setRecordTimer(recorder) {
    recorderTimeout = setTimeout(function () {
        document.getElementById("audioRecordButton").click();
        clearTimeout(recorderTimeout);
    }, 3600000);
}

async function postAudioRecording(blob) {
    event.preventDefault();
    var form = document.getElementById("userRecordedAudioForm");
    var formData = new FormData(form);
    formData.append("audio_data", blob, "audio.wav");
    formData.append("chatID", document.getElementById("chatIDValue").innerHTML);
    const response = await (await fetch("Chat?handler=UploadAudio", { method: "post", body: formData })).text();
    var jsonObject = JSON.parse(response);
    if (jsonObject.Success = true) {
        sendAudioMessage(jsonObject.chatID, jsonObject.messageID);
    } else {
        console.log(jsonObject);
    }
}

function stopRecording() {
    var button = document.getElementById("audioRecordButton");
    var isRecording = button.dataset.recording;
    clearTimeout(recorderTimeout);
    if (isRecording) {
        button.click();
    }   
}

function playPopSound() {
    new Audio("/audio/assets/pop.mp3").play();
}

错误出现在navigator.mediaDevices.getUserMedia(options)的catch回调中。

MainPage.xaml配置

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
             x:Class="CreatorGameMobile.MainPage">

    <Page.Behaviors>
        <toolkit:StatusBarBehavior x:Name="statusBar" />
    </Page.Behaviors>

    <WebView x:Name="webview"></WebView>
    

</ContentPage>

MainPage.xaml.cs代码

public MainPage()
{
    InitializeComponent();
    webview.Source = "https://creatorgame.app";
}

MainActivity.cs代码

protected override async void OnCreate(Bundle savedInstanceState)
{
    base.OnCreate(savedInstanceState);
    HandleIntent(Intent);
    CreateNotificationChannelIfNeeded();
}

测试页面为HTTPS协议站点。

请问我在WebView中开启麦克风权限还遗漏了哪些配置?


解决方案

你需要补充以下几个关键配置和代码修改:

1. 动态申请权限

Android 6.0及以上版本需要动态申请危险权限,即使在Manifest中声明了,也需要在运行时请求用户授权。修改MainActivity.cs,添加权限申请逻辑:

using Android.App;
using Android.Content.PM;
using Android.OS;

namespace YourAppNamespace;

[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
public class MainActivity : MauiAppCompatActivity
{
    private const int RequestAudioPermissions = 1;

    protected override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);
        HandleIntent(Intent);
        CreateNotificationChannelIfNeeded();
        
        // 动态申请录音权限
        RequestAudioPermission();
    }

    private void RequestAudioPermission()
    {
        if (CheckSelfPermission(Android.Manifest.Permission.RecordAudio) != Permission.Granted)
        {
            RequestPermissions(new[] { Android.Manifest.Permission.RecordAudio }, RequestAudioPermissions);
        }
    }

    // 处理权限申请结果
    public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
    {
        base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == RequestAudioPermissions)
        {
            if (grantResults.Length > 0 && grantResults[0] == Permission.Granted)
            {
                // 权限已授予,可正常使用麦克风
            }
        }
    }
}

2. 配置WebView的权限委托

MAUI的WebView需要设置PermissionsRequested事件,处理网页的权限请求,否则WebView会默认拒绝。修改MainPage.xaml.cs:

using Microsoft.Maui.Controls;

namespace YourAppNamespace;

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();
        webview.Source = "https://creatorgame.app";
        
        // 监听WebView权限请求
        webview.PermissionsRequested += Webview_PermissionsRequested;
    }

    private void Webview_PermissionsRequested(object sender, WebViewPermissionsRequestedEventArgs e)
    {
        foreach (var permission in e.Permissions)
        {
            // 允许麦克风权限请求
            if (permission.PermissionType == WebViewPermissionType.Microphone)
            {
                e.ShouldGrantPermission = true;
                break;
            }
        }
    }
}

3. 启用WebView的JavaScript和媒体支持

确保WebView启用了JavaScript和媒体相关设置,在MainPage.xaml中添加WebView的配置:

<WebView x:Name="webview" 
         JavaScriptEnabled="True"
         MediaPlaybackRequiresUserAction="False">
</WebView>

4. 修复JS代码中的错误

你的JS代码中start方法存在错误:writeToOutput(err)在catch块外面,此时err未定义,会导致报错。修改catch块:

navigator.mediaDevices.getUserMedia(options).then(function (stream) {
    myRecorder.objects.stream = stream;
    myRecorder.objects.recorder = new Recorder(
        myRecorder.objects.context.createMediaStreamSource(stream),
        { numChannels: 1 }
    );
    myRecorder.objects.recorder.record();
}).catch(function (err) { 
    writeToOutput(err); // 把错误处理移到catch块内
});
// 移除外面的writeToOutput(err);
setRecordTimer(myRecorder);
playPopSound();

完成以上配置后,WebView应该可以正常请求并获取麦克风权限了。


内容的提问来源于stack exchange,提问作者Jeremy Bean

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 06:25:19