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

基于PeerJS的WebRTC音视频App在Android 11上无音视频问题求助

适配Android 11及以上版本PeerJS音视频通话问题解决方案

安卓原生端配置修复

  • 针对SDK 31及以上版本,需要在AndroidManifest.xml中新增WebRTC相关权限声明:
<!-- 原有基础权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<!-- SDK31+新增必要权限 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
  • 相机、麦克风权限必须在加载WebView之前动态申请,不能仅在清单文件声明,也不要等到JS调用媒体接口时再申请,否则会出现权限获取成功但媒体流无法传输的问题。
  • 如果你的PeerJS服务使用HTTP而非HTTPS,需要在AndroidManifest.xml的<application>标签下添加android:usesCleartextTraffic="true",SDK31+默认禁止明文流量,会导致媒体流传输被拦截。

WebView配置调整

WebView初始化阶段需要开启WebRTC相关配置:

WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setAllowFileAccess(true);
webSettings.setAllowContentAccess(true);
// 允许媒体自动播放,无需用户手势触发
webSettings.setMediaPlaybackRequiresUserGesture(false);
// 授权WebRTC所需权限
webView.setWebChromeClient(new WebChromeClient() {
    @Override
    public void onPermissionRequest(PermissionRequest request) {
        // 提前完成原生权限申请后,此处直接授权即可,无需弹出系统授权框
        request.grant(request.getResources());
    }
});
// 禁用缓存避免跨版本兼容问题
webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
// 开启DOM存储
webSettings.setDomStorageEnabled(true);
// Android 11及以上专属兼容配置
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
    webSettings.setAllowFileAccessFromFileURLs(true);
    webSettings.setAllowUniversalAccessFromFileURLs(true);
}

JS代码适配修改

原有代码使用的废弃API和非标准字段是高版本内核兼容问题的核心诱因,修改后代码如下:

let localVideo = document.getElementById("local-video")
let remoteVideo = document.getElementById("remote-video")

let peer
var enableVideo
async function init(userId, isVideoCall) {
    enableVideo = isVideoCall === "true"
    peer = new Peer(userId, {
        host: '<My GCP server IP>',
        port: 9000,
        key: "myKey",
        path: '/calls',
        config: {
            // iceServers标准字段为urls,原有的url字段为非标准写法,高版本Chromium会忽略
            'iceServers': [
                { urls: 'stun:stun.ekiga.net' },
                { urls: 'stun:stun.schlund.de' },
                { urls: 'stun:stun.voipbuster.com' },
                { urls: 'stun:stun.voipstunt.com' },
                { urls: 'stun:stun.xten.com' },
                { urls: 'stun:stun.l.google.com:19302' },
                { urls: 'stun:stun1.l.google.com:19302' },
                { urls: 'stun:stun2.l.google.com:19302' },
                { urls: 'stun:stun3.l.google.com:19302' },
                { urls: 'stun:stun4.l.google.com:19302' },
                {
                    urls: 'turn:numb.viagenie.ca',
                    username: "<myUsername>",
                    credential: '<myPassword>'
                }
            ]
        }
    })
    peer.on('open', () => {
        Android.onPeerConnected()
    })

    peer.on('error', (err) => {
        console.log('Peer连接错误:', err)
        Android.onError()
    })
    listen()
}
let localStream;
function listen() {
    peer.on('call', async (call) => {
        try {
            // 替换废弃的navigator.getUserMedia为标准API
            const stream = await navigator.mediaDevices.getUserMedia({
                audio: true,
                video: enableVideo
            })
            localVideo.srcObject = stream
            localStream = stream
            call.answer(stream)
            call.on('stream', (remoteStream) => {
                Android.onCallConnected()
                remoteVideo.srcObject = remoteStream
                remoteVideo.className = "primary-video"
                localVideo.className = "secondary-video"
            })
            call.on('error', (err) => {
                console.log('通话错误:', err)
            })
        } catch (e) {
            console.log('媒体流获取失败:', e)
        }
    })
}

async function startCall(otherUserId) {
    try {
        // 替换废弃的navigator.getUserMedia为标准API
        const stream = await navigator.mediaDevices.getUserMedia({
            audio: true,
            video: enableVideo
        })
        localVideo.srcObject = stream
        localStream = stream
        const call = peer.call(otherUserId, stream)
        call.on('stream', (remoteStream) => {
            Android.onCallConnected()
            remoteVideo.srcObject = remoteStream
            remoteVideo.className = "primary-video"
            localVideo.className = "secondary-video"
        })
        call.on('error', (err) => {
            console.log('通话错误:', err)
        })
    } catch (e) {
        console.log('媒体流获取失败:', e)
    }
}

说明:ICE服务器配置使用非标准的url字段会导致高版本Chromium内核无法正常完成ICE协商,是你网络收发速率为0的核心原因之一。

其他验证点

  • 确认PeerJS服务端版本与客户端版本匹配,旧版本服务端不兼容高版本Chromium的WebRTC协议。
  • 如果使用HTTPS部署,确认SSL证书为受信任的有效证书,自签名证书在SDK31+的WebView中会被拦截,导致媒体流传输失败。
  • 部分三星设备的系统优化会限制后台应用的网络流量,需要为应用开启“允许后台使用流量”权限。

内容的提问来源于stack exchange,提问作者Sujith S Manjavana

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 12:39:02