私はリアルタイムのオーディオ/ビデオグループコールを実装しようとしていますが、今は2人の参加者にしか聞きたいことはありません。WebRTCエラー:リモートセッションの説明の作成に失敗しました。間違った状態で呼び出されました
私は実際に2つの異なるアカウントで同時にテストしていますが、いくつかのエラーメッセージが表示されますが、とにかく動作しますが、テストすると本当の異なるネットワークでの友人は、同じエラーメッセージが表示されますが、この場合には、我々は)聞いたり、お互いを見ることができません。
私は、Linux(Ubuntuの16.04)にクローム53を使用しています。
エラーメッセージがあります次のように、オファーを送信した同輩には、ブラウザコンソールに6つのエラーがあります。 1位:
Failed to create remote session description: OperationError: Failed to set remote offer sdp: Called in wrong state: STATE_SENTOFFER
第二、第三、第四、第五および:第六
addIceCandidate error: OperationError: Error processing ICE candidate
:
Failed to set local session description: OperationError: CreateAnswer failed because remote_description is not an offer
、ブラウザのコンソールで1つのエラーがあるのオファーを受け取り、送信して答えるピアの
:コンソール内のすべてのメッセージを表示したい場合にはFailed to create remote session description: OperationError: Failed to set remote answer sdp: Called in wrong state: STATE_INPROGRESS
、プランを送信したピアでのものはここにある:WebRTC error from offer peer。他のピアブラウザコンソールにあるものはWebRTC error from answer peerです。
HTMLファイルに重要なコードは(私は後で表示されるJavaScriptコードを持つ他のファイルがある)以下の通りです:
<div class='row'>
<div class='col-xs'>
<div class='box center-xs middle xs'>
<h1>Call CallNameExample</h1>
</div>
</div>
</div>
<div class='row'>
<div class='col-xs'>
<div class='box center-content'>
<button class='btn btn-info btn-37 no-padding circle' id='btnChangeCamStatus'>
<i class='material-icons' id='iconCamOff'>
videocam_off
</i>
<i class='material-icons hidden' id='iconCamOn'>
videocam
</i>
</button>
<button class='btn btn-info btn-37 no-padding circle' id='btnChangeMicStatus'>
<i aria-hidden='true' class='fa fa-microphone-slash' id='iconMicOff'></i>
<i aria-hidden='true' class='fa fa-microphone hidden' id='iconMicOn'></i>
</button>
</div>
</div>
</div>
<div class='row'>
<div class='col-xs'>
<div class='box center-xs middle xs'>
<video autoplay height='200px' id='bigRemoteVideo' width='200px'></video>
</div>
</div>
</div>
<script>
var room = "1"
var localVideo = document.getElementById("localVideo")
var bigRemoteVideo = document.getElementById("bigRemoteVideo")
document.getElementById("btnChangeCamStatus").addEventListener("click", function() {
if (localStream.getVideoTracks()[0].enabled) {
disableCam()
$("#iconCamOff").addClass("hidden")
$("#iconCamOn").removeClass("hidden")
} else {
enableCam()
$("#iconCamOff").removeClass("hidden")
$("#iconCamOn").addClass("hidden")
}
}, false);
document.getElementById("btnChangeMicStatus").addEventListener("click", function() {
if (localStream.getAudioTracks()[0].enabled) {
disableMic()
$("#iconMicOff").addClass("hidden")
$("#iconMicOn").removeClass("hidden")
} else {
enableMic()
$("#iconMicOff").removeClass("hidden")
$("#iconMicOn").addClass("hidden")
}
}, false);
function setLocalVideo(stream) {
localVideo.src = window.URL.createObjectURL(stream)
}
function setRemoteVideo(stream) {
bigRemoteVideo.src = window.URL.createObjectURL(stream)
}
localVideo.addEventListener('loadedmetadata', function() {
console.log('Local video videoWidth: ' + this.videoWidth +
'px, videoHeight: ' + this.videoHeight + 'px');
});
bigRemoteVideo.addEventListener('loadedmetadata', function() {
console.log('Remote video videoWidth: ' + this.videoWidth +
'px, videoHeight: ' + this.videoHeight + 'px');
});
// Starts the party:
(function(){
enableUserMedia()
window.createOrJoin(room)
console.log("Attempted to create or join room: " + room)
}())
</script>
他のJavaScriptファイルは、次のコード(すべてのファイルが含まれています一緒にここに):
var localStream
var mediaConstraints = {video: true, audio: true}
function enableUserMedia(){
console.log('Getting user media with constraints', mediaConstraints);
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia
if (navigator.getUserMedia) {
navigator.getUserMedia(mediaConstraints, gotStream, gotError)
}
window.URL = window.URL || window.webkitURL
function gotStream(stream) {
console.log('Adding local stream.');
setLocalVideo(stream)
localStream = stream;
//sendMessage('got user media');
console.log('got user media');
attachLocalMedia();
}
function gotError(error) {
console.log("navigator.getUserMedia error: ", error);
}
}
function disableCam(){
localStream.getVideoTracks()[0].enabled = false
}
function disableMic(){
localStream.getAudioTracks()[0].enabled = false
}
function enableCam(){
localStream.getVideoTracks()[0].enabled = true
}
function enableMic(){
localStream.getAudioTracks()[0].enabled = true
}
function disableUserMedia(){
localStream.getVideoTracks()[0].stop();
localStream.getAudioTracks()[0].stop();
}
window.onbeforeunload = function() {
sendMessage("bye");
};
function hangup() {
console.log("Hanging up.");
stop();
sendMessage("bye");
}
function handleRemoteHangup() {
console.log("Session terminated.");
stop();
}
function stop() {
disableUserMedia();
pc.close();
console.log("PC STATE: " + pc.signalingState || pc.readyState);
console.log("PC ICE STATE: " + pc.iceConnectionState)
pc = null;
}
var isInitiator = false
var justJoinedRoom = false
var sdpConstraints = { // Set up audio and video regardless of what devices are present.
'mandatory': {
'OfferToReceiveAudio': true,
'OfferToReceiveVideo': true
}
}
function sendMessage(message){
App.call.message(message);
}
function doCall() {
console.log("Sending offer to peer");
pc.createOffer(sdpConstraints)
.then(setLocalAndSendMessage)
.catch(handleCreateOfferError);
//pc.createOffer(setLocalAndSendMessage, handleCreateOfferError);
}
function doAnswer() {
console.log("Sending answer to peer.");
pc.createAnswer()
.then(setLocalAndSendMessage)
.catch(onSetLocalSessionDescriptionError);
}
function setLocalAndSendMessage(sessionDescription) {
console.log("setLocalAndSendMessage sending message" + JSON.stringify(sessionDescription));
pc.setLocalDescription(sessionDescription)
.then(
function(){
onSetLocalSuccess();
sendMessage(sessionDescription);
}
)
.catch(onSetLocalSessionDescriptionError);
}
function onSetLocalSuccess() {
console.log('setLocalDescription complete');
}
function onSetRemoteSuccess() {
console.log('setRemoteDescription complete');
doAnswer();
}
function onSetLocalSessionDescriptionError(error) {
console.error('Failed to set local session description: ' + error.toString())
}
function handleCreateOfferError(event) {
console.error("createOffer() error: " + JSON.stringify(event))
}
function onSetRemoteSessionDescriptionError(error) {
console.error("Failed to create remote session description: " + error.toString())
}
function handleReceivedOffer(message) {
console.log("handleReceivedOffer: " + JSON.stringify(message));
pc.setRemoteDescription(new RTCSessionDescription(message))
.then(onSetRemoteSuccess)
.catch(onSetRemoteSessionDescriptionError)
}
function handleReceivedAnswer(message) {
console.log("handleReceivedAnswer: " + JSON.stringify(message));
pc.setRemoteDescription(new RTCSessionDescription(message))
.then(onSetRemoteSuccess)
.catch(onSetRemoteSessionDescriptionError)
}
function handleReceivedCandidate(label, candidate) {
pc.addIceCandidate(
new RTCIceCandidate({
sdpMLineIndex: label,
candidate: candidate
})
).then(successAddingIceCandidate).catch(errorAddingIceCandidate)
}
function successAddingIceCandidate() { console.log("addIceCandidate successfully") }
function errorAddingIceCandidate(error) { console.error("addIceCandidate error: " + error.toString()) }
var remoteStream
var pc
var pcConfig = {
'iceServers': [{
'url': 'stun:stun.l.google.com:19302'
}, {
'url': 'turn:192.158.29.39:3478?transport=udp',
'credential': 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
'username': '28224511:1379330808'
}]
}
function connectionStateCallback(){
var state;
if (pc) {
state = pc.connectionState
console.log("PC CONNECTION state change callback, state: " + state)
}
}
function signalingStateCallback() {
var state;
if (pc) {
state = pc.signalingState || pc.readyState;
console.log("PC SIGNALING state change callback, state: " + state);
}
}
function iceStateCallback() {
var iceState;
if (pc) {
iceState = pc.iceConnectionState;
console.log('PC ICE connection state change callback, state: ' + iceState);
}
}
function createPeerConnection() {
try {
pc = new RTCPeerConnection(pcConfig);
signalingStateCallback();
pc.onsignalingstatechange = signalingStateCallback;
console.log("PC ICE STATE: " + pc.iceConnectionState);
pc.oniceconnectionstatechange = iceStateCallback;
pc.onconnectionstatechange = connectionStateCallback;
pc.onicecandidate = handleIceCandidate;
pc.onaddstream = handleRemoteStreamAdded;
pc.onremovestream = handleRemoteStreamRemoved;
console.log('Created RTCPeerConnnection');
attachLocalMedia();
} catch (e) {
console.error("Failed to create PeerConnection, exception: " + e.toString())
return;
}
}
function handleIceCandidate(event) {
console.log("icecandidate event: " + JSON.stringify(event));
if (event.candidate) {
sendMessage({
type: "candidate",
label: event.candidate.sdpMLineIndex,
id: event.candidate.sdpMid,
candidate: event.candidate.candidate
});
} else {
console.log("End of candidates.");
}
}
function handleRemoteStreamAdded(event) {
console.log("Remote stream added.");
setRemoteVideo(event.stream);
remoteStream = event.stream;
}
function handleRemoteStreamRemoved(event) { //In real life something should be done here but since the point of this website is to learn, this function is not a priority right now.
console.log("Remote stream removed. Event: " + event);
}
function attachLocalMedia() {
if (pc && localStream) {
pc.addStream(localStream)
console.log("Added localStream to pc")
if (justJoinedRoom) {
console.log("call to DOCALL() from attachLocalMedia()")
doCall()
}
}
}
最後に、コードは、シグナリングに関連しています。しかし、最初に私はRailsの5と、このウェブサイトおよびActionCable経由WebSocketを持つシグナリングをやって明確にしたいので、チャネルのためのCoffeeScriptファイル(クライアント側)は、このいずれかになります。
window.createOrJoin = (roomID) ->
App.call = App.cable.subscriptions.create { channel: "CallChannel", room: roomID },
connected: ->
# Called when the subscription is ready for use on the server
createPeerConnection()
disconnected: ->
# Called when the subscription has been terminated by the server
received: (data) ->
# Called when there's incoming data on the websocket for this channel
if (data["kindOfData"] == "created")
console.log('Created room ' + data["room"])
window.isInitiator = true # ESTO ME SIRVE SOLO PARA 2 PERSONAS!! # CREO QUE YA NI LO USO
attachLocalMedia()
else if (data["kindOfData"] == "full")
console.log('Room ' + data["room"] + ' is full')
else if (data["kindOfData"] == "join")
console.log('Another peer made a request to join room ' + data["room"])
console.log('This peer is the initiator of room ' + data["room"] + '!')
window.justJoinedRoom = false
else if (data["kindOfData"] == "joined")
console.log('joined: ' + data["room"])
window.justJoinedRoom = true
attachLocalMedia()
else if (data["kindOfData"] == "log")
console.log(data["info"])
else if (data["kindOfData"] == "message") # This client receives a message
console.log("Client received message: " + JSON.stringify(data["message"]));
if (data["message"] == "bye")
handleRemoteHangup()
else if (data["message"]["type"] == "offer")
handleReceivedOffer(data["message"]) # obj with "type" and "sdp"
else if (data["message"]["type"] == "answer")
handleReceivedAnswer(data["message"]) # obj with "type" and "sdp"
else if (data["message"]["type"] == "candidate")
handleReceivedCandidate(data["message"]["label"], data["message"]["candidate"])
message: (data) ->
console.log("Client sending message: " + JSON.stringify(data));
@perform "message", {message: data, room: roomID}
とRubyの1(サーバー側):
class CallChannel < ApplicationCable::Channel
def subscribed # Action automatically called when a client is subscribed to the channel
stream_from "calls" # calls is a channel in common for everyone # ONLY FOR TESTING!!!
stream_from "calls_room#{params[:room]}_person#{current_user.id}"
@@hashUsersByRoom ||= Hash.new() # { |h,k| h[k] = Set.new }
@@hashRoomsByUser ||= Hash.new() # { |h,k| h[k] = Set.new }
result = createOrJoin(params[:room])
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
def message(data)
if data["message"].eql? "bye"
if @@hashUsersByRoom[ data["room"] ] && @@hashUsersByRoom[ data["room"] ].include?(current_user.id)
@@hashUsersByRoom[ data["room"] ].delete(current_user.id)
if @@hashUsersByRoom[ data["room"] ].length() == 0
@@hashUsersByRoom.delete(data["room"])
Call.find(data["room"]).update_column("active", false)
end
end
if @@hashRoomsByUser[ current_user.id ] && @@hashRoomsByUser[ current_user.id ].include?(data["room"])
@@hashRoomsByUser[ current_user.id ].delete(data["room"])
if @@hashRoomsByUser[ current_user.id ].length() == 0
@@hashRoomsByUser.delete(current_user.id)
end
end
end
ActionCable.server.broadcast "calls_room#{data["room"]}", kindOfData: "log", info: "Client #{current_user.id} said: #{data["message"]}"
ActionCable.server.broadcast "calls_room#{data["room"]}", kindOfData: "message", message: data["message"]
end
private
def createOrJoin(room)
ActionCable.server.broadcast "calls", kindOfData: "log", info: "Received request to create or join room #{room}"
@@hashUsersByRoom[room] ||= Set.new()
ActionCable.server.broadcast "calls", kindOfData: "log", info: "Room #{room} now has #{@@hashUsersByRoom[room].length()} + client(s)"
if @@hashUsersByRoom[room].length == 0
stream_from "calls_room#{room}" # Join the room
@@hashUsersByRoom[ room ] << current_user.id
@@hashRoomsByUser[ current_user.id ] ||= Set.new()
@@hashRoomsByUser[ current_user.id ] << room
ActionCable.server.broadcast "calls", kindOfData: "log", info: "Client ID #{current_user.id} created room #{room}"
ActionCable.server.broadcast "calls_room#{room}_person#{current_user.id}", kindOfData: "created", room: room, user: current_user.id
Call.find(room).update_column("active", true)
elsif (@@hashUsersByRoom[room].length() < Call.where(:id => room).pluck(:maximumNumberOfParticipants)[0]) || (@@hashUsersByRoom[ data["room"] ].include?(current_user.id))
ActionCable.server.broadcast "calls", kindOfData: "log", info: "Client ID #{current_user.id} joined room #{room}"
ActionCable.server.broadcast "calls_room#{room}", kindOfData: "join", room: room
stream_from "calls_room#{room}" # Join the room
@@hashUsersByRoom[ room ] << current_user.id
@@hashRoomsByUser[ current_user.id ] ||= Set.new()
@@hashRoomsByUser[ current_user.id ] << room
ActionCable.server.broadcast "calls_room#{room}_person#{current_user.id}", kindOfData: "joined", room: room, user: current_user.id
ActionCable.server.broadcast "calls_room#{room}", kindOfData: "ready"
else # full room
ActionCable.server.broadcast "calls_room#{room}_person#{current_user.id}", kindOfData: "full", room: room
end
end
end
私は同様の問題を持つ人々を見たが、それぞれが別の理由のためだったし、それらのどれも私の状況のために有用ではなかったが、私は「STATE_INPROGRESS」手段」というどこかで見たインターネット上での検索オファー/回答交換が完了しましたので、オファー/アンサー交換が完了したかどうかわかりません...なぜ私が私達にお手上げしたらうまくいかないのですか?それは友人と一緒ですか?そのような場合にリモートセッションの説明を設定しようとしているのはなぜですか(オファー/アンサー交換が完了すると思われます)。 基本的に私の主な質問は、何が起こっているのですか、どのように解決できますか?
ご質問のこの部分にお越しいただいた場合は、ありがとうございます、私はそれを感謝します! :)
回答ありがとうございました:) はい、私はすでに本でそれを見ましたが、私が今までに言ったように、私は2人の参加者が必要で、エラーは2だけでテストしています。より多くの人々のためにそれを作ってくれるでしょう。なぜなら、シグナリングサーバでは、より多くの人々のために用意されていますが、クライアント側ではまだ見えません。なぜなら、私がそれを2のために働かせなければ、より多くの合併症を追加する。 – Marta
テストしたい場合、これは[ウェブサイト](https://lanformon.herokuapp.com/)です。これらのコールのいずれかに参加して2人で試すことができます(異なるブラウザまたは同じブラウザシークレットでは1つのウィンドウで2つのアカウントを使用できます)。私はちょうど2人のユーザーを作成しました。電子メールは "[email protected]"と "[email protected]"です。両方ともパスワード: "password" – Marta
2人のユーザーを必要とするかどうかは関係ありません。 5.それぞれに1つのpeerConnectionが必要です。 –