无法设置远程商品sdp:会话错误代码:ERROR_CONTENT

时间:2017-09-28 03:02:07

标签: node.js reactjs websocket safari webrtc

我在将原生Android浏览器中的点对点视频连接到safari 11桌面时遇到问题(反之亦然),这是错误:

未处理的Promise拒绝:OperationError(DOM异常34):无法设置远程提供sdp:会话错误代码:ERROR_CONTENT。会话错误说明:无法设置远程视频描述发送参数..

我目前陷入了这个问题

这是我的整个客户端视频聊天代码,谢谢。

import app from '../../../config';

const videoChatService = app.service('participants/video-chat');

let localVid;
let remoteVid;
let localstream;
let rtcPeerConn;
let conversationId;
let userId;
let isSdpSent = false;
let hasAddTrack;

const configuration = {
  iceServers: [{
    urls: 'stun:stun.l.google.com:19302',
  },
  {
    urls: 'stun:stun.services.mozilla.com',
    username: 'louis@mozilla.com',
    credential: 'webrtcdemo',
  },
  // {
  //   urls: 'turn:mdn-samples.mozilla.org',
  //   username: 'webrtc',
  //   credential: 'turnserver' }
  ] };

function closeVideoCall() {
  if (rtcPeerConn) {
    rtcPeerConn.onaddstream = null;
    rtcPeerConn.ontrack = null;
    rtcPeerConn.onremovestream = null;
    rtcPeerConn.onicecandidate = null;

    if (remoteVid.srcObject) {
      remoteVid.srcObject.getTracks().forEach(track => track.stop());
      remoteVid.srcObject = null;
    }

    if (localVid.srcObject) {
      localVid.srcObject.getTracks().forEach(track => track.stop());
      localVid.srcObject = null;
    }

    rtcPeerConn.close();
    rtcPeerConn = null;
  }
}

// set local sdp
function setLocalSDP(desc) {
  console.log('>>> setLocalSDP', rtcPeerConn);
  return rtcPeerConn.setLocalDescription(desc);
}

function logError(error) {
  console.log(`>>>>logError, ${error.name}: ${error.message}`);
}

function handleNegotiatedNeededEvent() {
  console.log('>>>>> on negotiation called');
  console.log('query >>>', conversationId, userId);
  if (!isSdpSent) {
    rtcPeerConn.createOffer()
      .then(setLocalSDP)
      .then(() => {
        isSdpSent = true;
        videoChatService.patch(null, {
          data: {
            type: 'video-offer',
            message: JSON.stringify(rtcPeerConn.localDescription),
          },
        }, {
          query: {
            conversation_id: conversationId,
            user_id: userId,
          },
        }).then().catch(e => { console.log('patch error', e); });
      })
      .catch(logError);
  }
}

function handleRemoveStreamEvent() {
  closeVideoCall();
}

function handleTrackEvent (evt) {
  console.log('>>>>> going to add their stream...', evt);
  remoteVid = document.getElementById('remoteStream');
  if (!remoteVid.srcObject) {
    remoteVid.srcObject = evt.streams[0];
  }
}

function handleAddStreamEvent(evt) {
  console.log('>>>>> stream added');
  remoteVid = document.getElementById('remoteStream');
  remoteVid.srcObject = event.stream;
}

function handleICECandidateEvent(evt) {
  console.log('>>>> onicecandidate', evt);
  console.log('query >>>', conversationId, userId);
  if (evt.candidate) {
    videoChatService.patch(null, {
      data: {
        type: 'new-ice-candidate',
        message: JSON.stringify(evt.candidate),
      },
    }, {
      query: {
        conversation_id: conversationId,
        user_id: userId,
      },
    });
  }
}

function handleICEConnectionStateChangeEvent() {
  console.log(`>>>>> ICE connection state changed to ${rtcPeerConn.iceConnectionState}`);

  switch (rtcPeerConn.iceConnectionState) {
    case 'closed':
    case 'failed':
    case 'disconnected':
      console.log('>>>> disconnected');
      closeVideoCall();
      break;
  }
}

function handleSignalingStateChangeEvent() {
  console.log(`>>>>> WebRTC signaling state changed to: ${rtcPeerConn.signalingState}`);
  switch (rtcPeerConn.signalingState) {
    case 'closed':
    console.log('>>>> closed');
      closeVideoCall();
      break;
  }
}

function createPeerConnection() {
  rtcPeerConn = new RTCPeerConnection(configuration);
  console.log('>>>>> create peer connection', rtcPeerConn);

  hasAddTrack = (rtcPeerConn.addTrack !== undefined);

  rtcPeerConn.onicecandidate = handleICECandidateEvent;
  rtcPeerConn.onnegotiationneeded = handleNegotiatedNeededEvent;
  rtcPeerConn.oniceconnectionstatechange = handleICEConnectionStateChangeEvent;
  rtcPeerConn.onsignalingstatechange = handleSignalingStateChangeEvent;
  rtcPeerConn.onremovestream = handleRemoveStreamEvent;

  if (hasAddTrack) {
    rtcPeerConn.ontrack = handleTrackEvent;
  } else {
    rtcPeerConn.onaddstream = handleAddStreamEvent;
  }
}

function handleGetUSerMediaError(e) {
  switch (e.name) {
    case 'NotFoundError':
      alert('Unable to open your call because no camera and/or microphone were found.');
      break;
    case 'SecurityError':
    case 'PermissionDeniedError':
      // Do nothing; this is the same as the user canceling the call.
      break;
    default:
      alert(`Error opening your camera and/or microphone: ${e.message}`);
      break;
  }
}

// add video to local and add to track
function gotStream(stream) {
  console.log('>>>> gotStream', stream);
  localVid.srcObject = stream;
  localstream = stream;

  if (hasAddTrack) {
    stream.getTracks().forEach(track => rtcPeerConn.addTrack(track, localstream));
  } else {
    rtcPeerConn.addStream(localstream);
  }
}

// start signaling
export function startSignaling(conversation_id, user_id) {
  localVid = document.getElementById('localStream');
  remoteVid = document.getElementById('remoteStream');
  console.log('>>>>> startSignaling');

  conversationId = conversation_id;
  userId = user_id;

  return () => {
    if (!rtcPeerConn) {
      createPeerConnection();
      navigator.mediaDevices.getUserMedia({
        audio: true,
        video: {
          facingMode: 'user',
        },
      })
        .then(gotStream)
        .catch(handleGetUSerMediaError);
    }
  };
}

export function handleVideoOfferMsg(conversation_id, user_id, message) {
  console.log('>>>>> handleVideoOfferMsg');
  localstream = null;
  conversationId = conversation_id;
  userId = user_id;
  localVid = document.getElementById('localStream');
  remoteVid = document.getElementById('remoteStream');
  return () => {
    createPeerConnection();
    console.log('query >>>', conversationId, userId);
    const sdp = new RTCSessionDescription(message);
    // sdp.sdp = sdp.replace('a=setup:active', 'a=setup:passive');
    rtcPeerConn.setRemoteDescription(sdp)
      .then(() => (
        navigator.mediaDevices.getUserMedia({
          audio: true,
          video: {
            facingMode: 'user',
          },
        })
      ))
      .then(gotStream)
      .then(() => (
        rtcPeerConn.createAnswer()
      ))
      .then(setLocalSDP)
      .then(() => {
        videoChatService.patch(null, {
          data: {
            type: 'video-answer',
            message: JSON.stringify(rtcPeerConn.localDescription),
          },
        }, {
          query: {
            conversation_id: conversationId,
            user_id: userId,
          },
        }).then().catch(e => { console.log('patch error', e); });
      });
  };
}

export function handleVideoAnswerMsg(message) {
  console.log('>>>>> handle video answer message', message);
  return () => {
    const sdp = new RTCSessionDescription(message);
    rtcPeerConn.setRemoteDescription(sdp)
      .catch(logError);
  };
}

// Adding ice candidate
export function addIceCandidate(message) {
  console.log('>>>> addIceCandidate', message);
  return () => {
    const candidate = new RTCIceCandidate(message);
    rtcPeerConn.addIceCandidate(candidate)
    .then(() => {
      console.log('>>> candidate added ');
    })
    .catch(e => {
      console.log('Error candidate', e);
    });
  };
}

3 个答案:

答案 0 :(得分:1)

Android上的Chrome与iOS / Safari之间的WebRTC连接有两个不同的问题:

1)设备上没有H.264实现

Chrome for Android只有H.264的硬件实现,没有软件实现。目前,H.264仅适用于配备Qualcomm(Kitkat及更高版本)或Samsung Exynos(Lollipop及更高版本)处理器的设备。由于Apple仅支持H.264,因此其他Android设备无法与iOS和Safari连接。

2)Chrome for Android存在错误:

Chrome Android does not offer/answer H.264 Constrained Baseline Profile

由于Apple仅支持H.264,因此Android / Chrome目前无法与iOS连接。

此问题将在Chrome Android 65(现为Canary)中解决。有关详细信息,请参阅this

我看到你的错误信息就是这个bug,所以我很确定这是问题所在。但最终它并不重要。但你应该意识到这两个问题。

答案 1 :(得分:0)

这可能与this issue有关。

Android上的Chrome并不总是支持H264,Safari只支持H264。

答案 2 :(得分:0)

要验证是否为视频编解码器问题(如其他答案所述),您可以调整流约束:

navigator.mediaDevices.getUserMedia({
  video: false,
  audio: true
})

如果此更改后ICE处理成功,则SDP可能包含另一端不支持的编解码器。

然后,您应该将代码扩展到仅在GUI中显示错误消息后才能回退到音频。