iOS Safari中Google Nest Cam Battery的WebRTC直播流无法加载问题
问题
我需要获取新款Google Nest Cam Battery的直播流,采用设备支持的WebRTC协议实现。该方案在安卓设备及PC端均可正常运行,但在iOS Safari中无法加载直播流。尝试过使用RTCPeerConnection、Simplepeer,也引入了adapter JS,但均无效果。
排查时在代码中添加alert后,能弹出alert“6”,但无法弹出“7”,说明在执行const data = await response.json();时解析响应JSON出现问题。
使用的核心代码如下:
<video id="webCamVideo" controls autoplay playsinline loop width="80%" /><br> <script src="webrtc.github.io/adapter/adapter-latest.js"></script> <script type="text/javascript"> const configuration = { iceServers: [ { urls: [ 'stun:stun1.l.google.com:19302', 'stun:stun2.l.google.com:19302', ], }, ], iceCandidatePoolSize: 10, }; let peerConnection = null; let remoteStream = null; async function startFeed() { peerConnection = new window.RTCPeerConnection(configuration); const token = '<?php echo($_COOKIE["acto"]); ?>'; const projectid = "<?php echo($project_id); ?>"; const deviceid = "<?php echo($deviceid); ?>"; remoteStream = new MediaStream(); const videoElement = document.querySelector('#webCamVideo'); if (videoElement) { videoElement.srcObject = remoteStream; } peerConnection.ontrack = event => { event.streams[0].getTracks().forEach(track => { remoteStream.addTrack(track); }); }; if (peerConnection.createDataChannel) { peerConnection.createDataChannel("dataSendChannel"); } try { const offer = await peerConnection.createOffer({ offerToReceiveAudio: true, offerToReceiveVideo: true }); await peerConnection.setLocalDescription(offer); // 尝试修正Safari的offer参数问题 let offerSdp = offer.sdp; offerSdp = offerSdp.replace('a=sendrecv', 'a=recvonly'); const url = "https://smartdevicemanagement.googleapis.com/v1/enterprises/" + projectid + "/devices/" + deviceid + ":executeCommand"; const options = { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token }, body: JSON.stringify({ "command": "sdm.devices.commands.CameraLiveStream.GenerateWebRtcStream", "params": { "offerSdp": offerSdp } }) }; const response = await fetch(url, options); // 此处解析JSON失败 const data = await response.json(); let answerSdp = data.results.answerSdp; const answer = new window.RTCSessionDescription({ type: 'answer', sdp: answerSdp }); await peerConnection.setRemoteDescription(answer); } catch (error) { console.error("Error starting feed:", error); } } window.addEventListener('load', startFeed); </script>
解决方案
针对iOS Safari中JSON解析失败的问题,按以下步骤排查修复:
1. 确认响应内容是否为合法JSON
在获取response后,先打印响应状态和原始文本,排查是否返回了错误页面而非预期JSON:
const response = await fetch(url, options); // 添加以下调试代码 console.log('响应状态码:', response.status); const responseText = await response.text(); console.log('响应原始文本:', responseText); // 再尝试解析JSON const data = JSON.parse(responseText);
如果打印出的是HTML错误页面(比如401未授权、403禁止访问),说明请求本身失败,而非JSON解析问题。
2. 手动处理HTTP错误状态
fetch仅在网络故障时才会抛出异常,HTTP错误(如4xx/5xx)不会自动reject,需手动判断:
const response = await fetch(url, options); if (!response.ok) { const errorText = await response.text(); throw new Error(`请求失败,状态码: ${response.status},响应内容: ${errorText}`); } const data = await response.json();
这样错误会被catch块捕获,能直接看到具体失败原因。
3. 修正Safari的SDP替换逻辑
当前仅替换了第一个a=sendrecv,但Safari生成的SDP中可能存在多个该字段,需全局替换:
// 把原替换代码改为全局正则替换 offerSdp = offerSdp.replace(/a=sendrecv/g, 'a=recvonly');
确保所有媒体流方向都被设置为recvonly,避免Google API返回错误响应。
4. 检查iOS Safari的Token有效性
iOS Safari的Cookie策略更严格,可能导致$_COOKIE["acto"]获取到的Token无效或为空。在代码中打印Token值确认:
const token = '<?php echo($_COOKIE["acto"]); ?>'; console.log('当前Token:', token);
如果Token为空或无效,需调整Cookie的跨域/存储策略,确保iOS Safari能正确获取到授权Token。
5. 适配Safari的Data Channel创建时机
部分版本的Safari对createDataChannel的调用时机敏感,建议在setLocalDescription成功后再创建数据通道:
await peerConnection.setLocalDescription(offer); // 移到此处创建数据通道 if (peerConnection.createDataChannel) { peerConnection.createDataChannel("dataSendChannel"); }
内容的提问来源于stack exchange,提问作者Eren TURKOGLU

