Javascript WebRT C Не удалось установить удаленный ответ. Sdp: вызван в неправильном состоянии: kHaveRemoteOffer и вызван в неправильном состоянии: kStable - PullRequest
2 голосов
/ 11 марта 2020

Я не могу заставить мой код WebRT C работать должным образом ... Я все сделал правильно, я считаю, и он все еще не работает. Есть что-то странное, почему ontrack вызывается так рано, может быть, так оно и есть.

Сайт использует javascript код, код сервера, который я не публиковал, но здесь соединение WebSockets - просто обменник, то, что вы отправляете на сервер, отправляет ту же информацию другому партнеру (незнакомцу), с которым вы тоже связаны.

Код сервера выглядит так:

    private void writeStranger(UserProfile you, String msg) {
        UserProfile stranger = you.stranger;
        if(stranger != null)
            sendMessage(stranger.getWebSocket(), msg);
    }

    public void sendMessage(WebSocket websocket, String msg) {
        try {
            websocket.send(msg);
        } catch ( WebsocketNotConnectedException e ) {
            disconnnectClient(websocket);
        }
    }

   //...

        case "ice_candidate":
            JSONObject candidatePackage = (JSONObject) packet.get(1);
            JSONObject candidate = (JSONObject) candidatePackage.get("candidate");

            obj = new JSONObject();
            list = new JSONArray();

            list.put("iceCandidate");
            obj.put("candidate", candidate);
            list.put(obj);

            System.out.println("Sent = " + list.toString());

            writeStranger(you, list.toString()); //send ice candidate to stranger

            break;
        case "send_answer":
            JSONObject sendAnswerPackage = (JSONObject) packet.get(1);
            JSONObject answer = (JSONObject) sendAnswerPackage.get("answer");

            obj = new JSONObject();
            list = new JSONArray();

            list.put("getAnswer");
            obj.put("answer", answer);
            list.put(obj);

            System.out.println("Sent = " + list.toString());

            writeStranger(you, list.toString()); //send answer to stranger

            break;
        case "send_offer":
            JSONObject offerPackage = (JSONObject) packet.get(1);
            JSONObject offer = (JSONObject) offerPackage.get("offer");

            obj = new JSONObject();
            list = new JSONArray();

            list.put("getOffer");
            obj.put("offer", offer);
            list.put(obj);

            System.out.println("Sent = " + list.toString());

            writeStranger(you, list.toString()); //send ice candidate to stranger

            break;

Вот мои выводы.
RAW Текст: https://pastebin.com/raw/FL8g29gG
JSON цветной: https://pastebin.com/FL8g29gG

Мой javascript Код ниже

var ws;

var peerConnection, localStream;    
var rtc_server = {
  iceServers: [
                {urls: "stun:stun.l.google.com:19302"},
                {urls: "stun:stun.services.mozilla.com"},
                {urls: "stun:stun.stunprotocol.org:3478"},
                {url: "stun:stun.l.google.com:19302"},
                {url: "stun:stun.services.mozilla.com"},
                {url: "stun:stun.stunprotocol.org:3478"},
  ]
}

//offer SDP's tells other peers what you would like
var rtc_media_constraints = {
  mandatory: {
    OfferToReceiveAudio: true,
    OfferToReceiveVideo: true
  }
};

var rtc_peer_options = {
  optional: [
              {DtlsSrtpKeyAgreement: true}, //To make Chrome and Firefox to interoperate.
  ]
}

var PeerConnection = RTCPeerConnection || window.PeerConnection || window.webkitPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection;
var IceCandidate = RTCIceCandidate || window.mozRTCIceCandidate || window.RTCIceCandidate;
var SessionDescription = RTCSessionDescription || window.mozRTCSessionDescription || window.RTCSessionDescription;
var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;

function hasSupportForVideoChat() {
   return window.RTCPeerConnection && window.RTCIceCandidate && window.RTCSessionDescription && navigator.mediaDevices && navigator.mediaDevices.getUserMedia && (RTCPeerConnection.prototype.addStream || RTCPeerConnection.prototype.addTrack) ? true : false;
}

function loadMyCameraStream() {
    if (getUserMedia) {
      getUserMedia.call(navigator, { video: {facingMode: "user", aspectRatio: 4 / 3/*height: 272, width: 322*/}, audio: { echoCancellation : true } },
        function(localMediaStream) {
          //Add my video
          $("div#videoBox video#you")[0].muted = true;
          $("div#videoBox video#you")[0].autoplay = true;
          $("div#videoBox video#you").attr('playsinline', '');
          $("div#videoBox video#you").attr('webkit-playsinline', '');
          $("div#videoBox video#you")[0].srcObject = localMediaStream;
          localStream = localMediaStream;
        },
        function(e) {
          addStatusMsg("Your Video has error : " + e);
        }
      );
    } else {
      addStatusMsg("Your browser does not support WebRTC (Camera/Voice chat).");
      return;
    }
}

function loadStrangerCameraStream() {
    if(!hasSupportForVideoChat())
      return;

    peerConnection = new PeerConnection(rtc_server, rtc_peer_options);
    if (peerConnection.addTrack !== undefined)
      localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream));
    else
      peerConnection.addStream(localStream);

    peerConnection.onicecandidate = function(e) {
      if (!e || !e.candidate)
        return;
      ws.send(JSON.stringify(['ice_candidate', {"candidate": e.candidate}]));
    };

    if (peerConnection.addTrack !== undefined) {
      //newer technology
      peerConnection.ontrack = function(e) {
        //e.streams.forEach(stream => doAddStream(stream));
        addStatusMsg("ontrack called");
        //Add stranger video
        $("div#videoBox video#stranger").attr('playsinline', '');
        $("div#videoBox video#stranger").attr('webkit-playsinline', '');
        $('div#videoBox video#stranger')[0].srcObject = e.streams[0];
        $("div#videoBox video#stranger")[0].autoplay = true;
      };
    } else {
      //older technology
      peerConnection.onaddstream = function(e) {
        addStatusMsg("onaddstream called");
        //Add stranger video
        $("div#videoBox video#stranger").attr('playsinline', '');
        $("div#videoBox video#stranger").attr('webkit-playsinline', '');
        $('div#videoBox video#stranger')[0].srcObject = e.stream;
        $("div#videoBox video#stranger")[0].autoplay = true;
      };
    }

    peerConnection.createOffer(
      function(offer) {
        peerConnection.setLocalDescription(offer, function () {
          //both offer and peerConnection.localDescription are the same.
          addStatusMsg('createOffer, localDescription: ' + JSON.stringify(peerConnection.localDescription));
          //addStatusMsg('createOffer, offer: ' + JSON.stringify(offer));
          ws.send(JSON.stringify(['send_offer', {"offer": peerConnection.localDescription}]));
        },
        function(e) {
          addStatusMsg('createOffer, set description error' + e);
        });
      },
      function(e) {
        addStatusMsg("createOffer error: " + e);
      },
      rtc_media_constraints
    );
}

function closeStrangerCameraStream() {
    $('div#videoBox video#stranger')[0].srcObject = null
    if(peerConnection)
      peerConnection.close();
}     

function iceCandidate(candidate) {
  //ICE = Interactive Connectivity Establishment
  if(peerConnection)
    peerConnection.addIceCandidate(new IceCandidate(candidate));
  else
    addStatusMsg("peerConnection not created error");
  addStatusMsg("Peer Ice Candidate = " + JSON.stringify(candidate));
}

function getAnswer(answer) {    
    if(!hasSupportForVideoChat())
      return;

    if(peerConnection) {
  peerConnection.setRemoteDescription(new SessionDescription(answer), function() {
    console.log("get answer ok");
    addStatusMsg("peerConnection, SessionDescription answer is ok");
  },
  function(e) {
    addStatusMsg("peerConnection, SessionDescription fail error: " + e);
  });
    }
}

function getOffer(offer) {
    if(!hasSupportForVideoChat())
      return;
    addStatusMsg("peerConnection, setRemoteDescription offer: " + JSON.stringify(offer));
    if(peerConnection) {
      peerConnection.setRemoteDescription(new SessionDescription(offer), function() {
        peerConnection.createAnswer(
          function(answer) {
            peerConnection.setLocalDescription(answer);
            addStatusMsg("create answer sent: " + JSON.stringify(answer));
            ws.send(JSON.stringify(['send_answer', {"answer": answer}]));
          },
          function(e) {
            addStatusMsg("peerConnection, setRemoteDescription create answer fail: " + e);
          }
        );
      });
    }
}

1 Ответ

2 голосов
/ 12 марта 2020

Мой веб-сайт, на котором я его использую: https://www.camspark.com/
Исправил себя. Я понял, что у меня есть 2 проблемы с этим кодом.

Первая проблема заключалась в том, что createOffer () должен был только быть отправленным одним человеком, а не обоими людьми. Вы должны случайно выбрать человека, который выполняет createOffer ().

Вторая проблема заключается в том, что кандидат ICE должен создать очередь / массив для обеих сторон, который содержит все входящие ледяные кандидаты. peerConnection.addIceCandidate(new IceCandidate(candidate)); выполняется только тогда, когда получен ответ на createOffer () и настроен ответ setRemoteDescription из createOffer().

И getAnswer (), и getOffer () используют точно один и тот же код, но один получен для 1 клиента, а другой получен для другого клиента. Обе должны использовать flu sh массив IceCandidates при срабатывании любой из них. Возможно, если кто-то захочет, вы можете объединить обе функции в одну функцию, поскольку код один и тот же.

Окончательный рабочий код выглядит следующим образом

var ws;

var peerConnection, localStream;  
//STUN = (Session Traversal Utilities for NAT)  
var rtc_server = {
  iceServers: [
                {urls: "stun:stun.l.google.com:19302"},
                {urls: "stun:stun.services.mozilla.com"},
                {urls: "stun:stun.stunprotocol.org:3478"},
                {url: "stun:stun.l.google.com:19302"},
                {url: "stun:stun.services.mozilla.com"},
                {url: "stun:stun.stunprotocol.org:3478"},
  ]
}

//offer SDP = [Session Description Protocol] tells other peers what you would like
var rtc_media_constraints = {
  mandatory: {
    OfferToReceiveAudio: true,
    OfferToReceiveVideo: true
  }
};

var rtc_peer_options = {
  optional: [
              {DtlsSrtpKeyAgreement: true}, //To make Chrome and Firefox to interoperate.
  ]
}
var finishSDPVideoOffer = false;
var isOfferer = false;
var iceCandidates = [];
var PeerConnection = RTCPeerConnection || window.PeerConnection || window.webkitPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection;
var IceCandidate = RTCIceCandidate || window.mozRTCIceCandidate || window.RTCIceCandidate;
var SessionDescription = RTCSessionDescription || window.mozRTCSessionDescription || window.RTCSessionDescription;
var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;

function hasSupportForVideoChat() {
   return window.RTCPeerConnection && window.RTCIceCandidate && window.RTCSessionDescription && navigator.mediaDevices && navigator.mediaDevices.getUserMedia && (RTCPeerConnection.prototype.addStream || RTCPeerConnection.prototype.addTrack) ? true : false;
}

function loadMyCameraStream() {
    if (getUserMedia) {
      getUserMedia.call(navigator, { video: {facingMode: "user", aspectRatio: 4 / 3/*height: 272, width: 322*/}, audio: { echoCancellation : true } },
        function(localMediaStream) {
          //Add my video
          $("div#videoBox video#you")[0].muted = true;
          $("div#videoBox video#you")[0].autoplay = true;
          $("div#videoBox video#you").attr('playsinline', '');
          $("div#videoBox video#you").attr('webkit-playsinline', '');
          $("div#videoBox video#you")[0].srcObject = localMediaStream;
          localStream = localMediaStream;
        },
        function(e) {
          addStatusMsg("Your Video has error : " + e);
        }
      );
    } else {
      addStatusMsg("Your browser does not support WebRTC (Camera/Voice chat).");
      return;
    }
}

function loadStrangerCameraStream(isOfferer_) {
    if(!hasSupportForVideoChat())
      return;

    //Only add pending ICE Candidates when getOffer() is finished.
    finishSDPVideoOfferOrAnswer = false;
    iceCandidates = []; //clear ICE Candidates array.
    isOfferer = isOfferer_;

    peerConnection = new PeerConnection(rtc_server, rtc_peer_options);
    if (peerConnection.addTrack !== undefined)
      localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream));
    else
      peerConnection.addStream(localStream);

    peerConnection.onicecandidate = function(e) {
      if (!e || !e.candidate)
        return;    
      ws.send(JSON.stringify(['ice_candidate', {"candidate": e.candidate}]));
    };

    if (peerConnection.addTrack !== undefined) {
      //newer technology
      peerConnection.ontrack = function(e) {
        //e.streams.forEach(stream => doAddStream(stream));
        addStatusMsg("ontrack called");
        //Add stranger video
        $("div#videoBox video#stranger").attr('playsinline', '');
        $("div#videoBox video#stranger").attr('webkit-playsinline', '');
        $('div#videoBox video#stranger')[0].srcObject = e.streams[0];
        $("div#videoBox video#stranger")[0].autoplay = true;
      };
    } else {
      //older technology
      peerConnection.onaddstream = function(e) {
        addStatusMsg("onaddstream called");
        //Add stranger video
        $("div#videoBox video#stranger").attr('playsinline', '');
        $("div#videoBox video#stranger").attr('webkit-playsinline', '');
        $('div#videoBox video#stranger')[0].srcObject = e.stream;
        $("div#videoBox video#stranger")[0].autoplay = true;
      };
    }

    if(isOfferer) {
      peerConnection.createOffer(
        function(offer) {
          peerConnection.setLocalDescription(offer, function () {
            //both offer and peerConnection.localDescription are the same.
            addStatusMsg('createOffer, localDescription: ' + JSON.stringify(peerConnection.localDescription));
            //addStatusMsg('createOffer, offer: ' + JSON.stringify(offer));
            ws.send(JSON.stringify(['send_offer', {"offer": peerConnection.localDescription}]));
          },
          function(e) {
            addStatusMsg('createOffer, set description error' + e);
          });
        },
        function(e) {
          addStatusMsg("createOffer error: " + e);
        },
        rtc_media_constraints
      );
    }
}

function closeStrangerCameraStream() {
    $('div#videoBox video#stranger')[0].srcObject = null
    if(peerConnection)
      peerConnection.close();
}     

function iceCandidate(candidate) {
  //ICE = Interactive Connectivity Establishment
  if(!finishSDPVideoOfferOrAnswer) {
    iceCandidates.push(candidate);
    addStatusMsg("Queued iceCandidate");
    return;
  }

  if(!peerConnection) {
    addStatusMsg("iceCandidate peerConnection not created error.");
    return;
  }

  peerConnection.addIceCandidate(new IceCandidate(candidate));
  addStatusMsg("Added on time, Peer Ice Candidate = " + JSON.stringify(candidate));
}

function getAnswer(answer) {    
    if(!hasSupportForVideoChat())
      return;

    if(!peerConnection) {
      addStatusMsg("getAnswer peerConnection not created error.");
      return;
    }

    peerConnection.setRemoteDescription(new SessionDescription(answer), function() {
      addStatusMsg("getAnswer SessionDescription answer is ok");
      finishSDPVideoOfferOrAnswer = true;
      while (iceCandidates.length) {
        var candidate = iceCandidates.shift();
        try {
          peerConnection.addIceCandidate(new IceCandidate(candidate));
          addStatusMsg("Adding queued ICE Candidates");
        } catch(e) {
          addStatusMsg("Error adding queued ICE Candidates error:" + e);
        }
      }
      iceCandidates = [];
    },
    function(e) {
      addStatusMsg("getAnswer SessionDescription fail error: " + e);
    });
}

function getOffer(offer) {
    if(!hasSupportForVideoChat())
      return;

    if(!peerConnection) {
      addStatusMsg("getOffer peerConnection not created error.");
      return;
    }

    addStatusMsg("getOffer setRemoteDescription offer: " + JSON.stringify(offer));
    peerConnection.setRemoteDescription(new SessionDescription(offer), function() {
      finishSDPVideoOfferOrAnswer = true;
      while (iceCandidates.length) {
        var candidate = iceCandidates.shift();
        try {
          peerConnection.addIceCandidate(new IceCandidate(candidate));
          addStatusMsg("Adding queued ICE Candidates");
        } catch(e) {
          addStatusMsg("Error adding queued ICE Candidates error:" + e);
        }
      }
      iceCandidates = [];
      if(!isOfferer) {
        peerConnection.createAnswer(
          function(answer) {
            peerConnection.setLocalDescription(answer);
            addStatusMsg("getOffer create answer sent: " + JSON.stringify(answer));
            ws.send(JSON.stringify(['send_answer', {"answer": answer}]));
          },
          function(e) {
            addStatusMsg("getOffer setRemoteDescription create answer fail: " + e);
          }
        );
      }
    });
}

Вот патч, который я сделал на серверном сервере WebSocket (Java).

//JSON
 //["connected", {videoChatOfferer: true}]
 //["connected", {videoChatOfferer: false}]
 JSONObject obj = new JSONObject();
 JSONArray list = new JSONArray();
 list.put("loadStrangerCameraStream");
 obj.put("videoChatOfferer", true); //first guy offerer for WebRTC.
 list.put(obj);
 server.sendMessage(websocket, list.toString()); //connected to chat partner
 obj.put("videoChatOfferer", false); //second guy isn't offerer.
 list.put(obj);
 server.sendMessage(stranger.getWebSocket(), list.toString()); //connected to chat partner
...